takeone-event-managment/doc/wi-fi connectivity guide.md

10 KiB
Raw Blame History

🌐 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

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:<ip>:<port>:<court_id>
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:

  • Court 1: TKD_COURT1
  • Court 2: TKD_COURT2
  • Devices are pre-configured to connect only to their courts 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++)

#include <WiFi.h>
#include <WiFiUdp.h>

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)

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)

using System.Net;
using System.Net.Sockets;

public class TcpServer
{
    private TcpListener _listener;
    private Dictionary<string, TcpClient> _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<string, string> ParseIdentity(string line)
    {
        var result = new Dictionary<string, string>();
        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