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

385 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 🌐 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:<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:
### ✅ 1. Unique SSID per Court (Recommended)
- **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++)
```cpp
#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)
```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<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 |