# 🌐 Taekwondo Scoring System – Wi-Fi Connectivity Guide > **Version:** 1.0 > **Last Updated:** July 2025 > **Target Developer:** Embedded & Android developers > **Purpose:** Detailed guide on **Wi-Fi connectivity** for single and multi-court setups with **auto-discovery and conflict prevention** --- ## 📌 Table of Contents - [1. Overview](#1-overview) - [2. Mode 1: Wi-Fi Hotspot-Based (Single Court)](#2-mode-1-wi-fi-hotspot-based-single-court) - [3. Mode 2: Wi-Fi Non-Hotspot (Tournament Mode)](#3-mode-2-wi-fi-non-hotspot-tournament-mode) - [4. Auto-Discovery Mechanism](#4-auto-discovery-mechanism) - [5. Multi-Court Safety & Conflict Prevention](#5-multi-court-safety--conflict-prevention) - [6. ESP32 Code Examples](#6-esp32-code-examples) - [7. Android .NET Server Code](#7-android-net-server-code) - [8. Best Practices](#8-best-practices) --- ## 1. Overview This guide details **two Wi-Fi connection modes** for the **Taekwondo Scoring System**: | Mode | Use Case | Network Control | |------|---------|----------------| | **1. Hotspot-Based** | Clubs, single court | Tablet creates Wi-Fi | | **2. Non-Hotspot (Client)** | Tournaments, multi-court | Tablet joins existing Wi-Fi | Both modes use **auto-discovery** so devices (sensors, joysticks, TVs) can **find the server without IP input**. Key goals: - ✅ Zero-config for non-technical users - ✅ Reliable auto-discovery - ✅ Prevent cross-talk between courts - ✅ Secure and scalable --- ## 2. Mode 1: Wi-Fi Hotspot-Based (Single Court) Ideal for **training clubs** or **local events** with **one court**. ### 📶 Network Setup - **Score Control Tablet** creates a Wi-Fi hotspot: - **SSID**: `TKD_SCORING_COURT1` - **Password**: `taekwondo123` - **IP**: `192.168.4.1` (default hotspot IP) - All devices connect to this network. ### ✅ Advantages - No dependency on venue Wi-Fi - Fixed server IP: `192.168.4.1` - Fast, stable, low-latency - Simple for staff: "Connect to TKD_SCORING_COURT1" ### 🔄 Auto-Discovery Flow ``` [ESP32 Device] → Connects to TKD_SCORING_COURT1 ↓ Sends UDP broadcast: TKD_DISCOVER ↓ [Tablet] ← Responds: SERVER_IP:192.168.4.1 ↓ Device connects to 192.168.4.1:5000 ``` > Since the tablet is the **only router**, it's always the **first IP** (`192.168.4.1`). --- ## 3. Mode 2: Wi-Fi Non-Hotspot (Tournament Mode) Used in **multi-court tournaments** where multiple score systems run **on the same venue Wi-Fi**. ### 📶 Network Setup - **Score Control Tablet** connects to **existing Wi-Fi** (e.g., `Venue_Guest`) - Gets IP via DHCP (e.g., `192.168.1.100`) - Starts TCP server on Port `5000` - Devices must **discover** the server IP ### ⚠️ Challenge - Server IP is **not fixed** - Devices must **find the correct server** among many ### ✅ Solution: **UDP Broadcast Discovery** Devices send a broadcast to find any active **Taekwondo scoring server**. --- ## 4. Auto-Discovery Mechanism To enable auto-discovery in **both modes**, we use **UDP broadcast**. ### 📡 Discovery Protocol | Step | Message | |------|--------| | 1 | Device sends: `TKD_DISCOVER` | | 2 | Server responds: `SERVER_IP:::` | | 3 | Device connects to `ip:port` | #### Example Response ``` SERVER_IP:192.168.1.100:5000:COURT1 ``` This allows devices to: - Find the server - Know which court they belong to --- ### 🔄 Discovery Flow (Non-Hotspot Mode) ``` [ESP32 Device] → Joins venue Wi-Fi ↓ Sends UDP broadcast: TKD_DISCOVER ↓ [Court 1 Server] ← Responds: SERVER_IP:192.168.1.100:5000:COURT1 [Court 2 Server] ← Responds: SERVER_IP:192.168.1.101:5000:COURT2 ↓ [Device] → Uses SSID or config to choose COURT1 → connects ``` > Devices only connect to the server matching their **court ID**. --- ## 5. Multi-Court Safety & Conflict Prevention To prevent **cross-talk** between courts: ### ✅ 1. Unique SSID per Court (Recommended) - **Court 1**: `TKD_COURT1` - **Court 2**: `TKD_COURT2` - Devices are **pre-configured** to connect only to their court’s SSID > ✅ **Best for tournaments** — no discovery conflicts ### ✅ 2. Court ID in Discovery Response - Server includes `COURT1`, `COURT2` in response - Device validates: only connect if `court_id` matches ### ✅ 3. MAC Address Whitelisting - Each server has a **pre-loaded list** of allowed device MACs - Blocks devices from other courts ### ✅ 4. Separate UDP Ports (Optional) - Court 1 listens on UDP `5002` - Court 2 listens on UDP `5003` - Prevents broadcast overlap --- ### 🛡️ Safety Summary | Risk | Solution | |------|----------| | Device connects to wrong court | Unique SSID or Court ID check | | Two tablets respond to discovery | Use Court ID filtering | | Unauthorized device connects | MAC whitelisting | | IP conflict | Let DHCP handle it | --- ## 6. ESP32 Code Examples ### 📡 ESP32: Auto-Discovery & Connect (Arduino C++) ```cpp #include #include const char* ssid = "TKD_SCORING_COURT1"; // Change per court const char* password = "taekwondo123"; const int udpPort = 5002; const char* discoverMsg = "TKD_DISCOVER"; const char* expectedCourt = "COURT1"; WiFiUDP udp; String getServerIP() { udp.beginPacket("255.255.255.255", udpPort); udp.print(discoverMsg); udp.endPacket(); unsigned long timeout = millis() + 3000; while (millis() < timeout) { int len = udp.parsePacket(); if (len > 0) { char response[64]; udp.read(response, len); response[len] = '\0'; // Parse: SERVER_IP:192.168.1.100:5000:COURT1 String resp = String(response); int ipStart = resp.indexOf("SERVER_IP:") + 10; int courtStart = resp.lastIndexOf(":") + 1; String courtId = resp.substring(courtStart); if (courtId == expectedCourt) { int portEnd = resp.lastIndexOf(":"); int ipEnd = resp.lastIndexOf(":", portEnd - 1); return resp.substring(ipStart, ipEnd); } } delay(100); } return ""; } void setup() { Serial.begin(115200); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } udp.begin(udpPort + 1); // Use different port for listening String serverIP = getServerIP(); if (serverIP != "") { Serial.println("Found server: " + serverIP); // Now connect to TCP server WiFiClient client; if (client.connect(serverIP.c_str(), 5000)) { client.println("DEVICE:NAME=HOGU_RED,TYPE=SENSOR,MAC=" + WiFi.macAddress()); // Send data... } } else { Serial.println("No server found!"); } } ``` --- ## 7. Android .NET Server Code ### 🖥️ Android: UDP Discovery Server (C# .NET MAUI) ```csharp using System.Net; using System.Net.Sockets; using System.Text; public class UdpDiscoveryServer { private UdpClient _udp; private string _serverIp; private string _courtId; private int _tcpPort = 5000; private int _udpPort = 5002; public UdpDiscoveryServer(string courtId) { _courtId = courtId; _serverIp = GetLocalIPAddress(); // e.g., 192.168.1.100 } public void Start() { _udp = new UdpClient(_udpPort); Task.Run(async () => { while (true) { var remote = new IPEndPoint(IPAddress.Any, 0); try { byte[] data = _udp.Receive(ref remote); string request = Encoding.UTF8.GetString(data); if (request.Trim() == "TKD_DISCOVER") { string response = $"SERVER_IP:{_serverIp}:{_tcpPort}:{_courtId}"; byte[] responseData = Encoding.UTF8.GetBytes(response); _udp.Send(responseData, responseData.Length, remote); } } catch (Exception ex) { // Handle error } } }); } private string GetLocalIPAddress() { // Get Wi-Fi IP (simplified) // In practice, use ConnectivityManager or WifiManager return "192.168.4.1"; // Or get dynamically } } ``` ### 🖥️ Android: TCP Server (Score Control App) ```csharp using System.Net; using System.Net.Sockets; public class TcpServer { private TcpListener _listener; private Dictionary _clients = new(); public async Task Start(int port) { _listener = new TcpListener(IPAddress.Any, port); _listener.Start(); while (true) { TcpClient client = await _listener.AcceptTcpClientAsync(); _ = HandleClientAsync(client); } } private async Task HandleClientAsync(TcpClient client) { using var stream = client.GetStream(); using var reader = new StreamReader(stream); string identity = await reader.ReadLineAsync(); if (identity?.StartsWith("DEVICE:") == true) { var parts = ParseIdentity(identity); string mac = parts["MAC"]; string name = parts["NAME"]; // Validate MAC against whitelist if (IsAllowed(mac)) { _clients[mac] = client; // Start reading data... } else { // Send DENIED and close var writer = new StreamWriter(stream); await writer.WriteLineAsync("DENIED"); await writer.FlushAsync(); client.Close(); } } } private Dictionary ParseIdentity(string line) { var result = new Dictionary(); if (!line.StartsWith("DEVICE:")) return result; var parts = line.Substring(7).Split(','); foreach (var part in parts) { var kv = part.Split('=', 2); if (kv.Length == 2) result[kv[0]] = kv[1]; } return result; } private bool IsAllowed(string mac) => AllowedDevices.Contains(mac); } ``` --- ## 8. Best Practices | Practice | Why | |--------|-----| | **Use unique SSID per court** | Prevents discovery conflicts | | **Pre-configure device Wi-Fi** | No user setup needed | | **Include Court ID in discovery** | Safe multi-court operation | | **Whitelist device MACs** | Prevents spoofing | | **Use UDP broadcast on 5002+** | Avoids port conflicts | | **Fixed hotspot IP (192.168.4.1)** | Simplifies client logic | | **Reconnect logic on ESP32** | Handles Wi-Fi drops | | **Log connection events** | Debugging and auditing |