Add doc/WiFiConnectivity.md

This commit is contained in:
Ghassan Yusuf 2025-07-23 14:58:35 +03:00
parent 17a1dbb784
commit e03d71a163

411
doc/WiFiConnectivity.md Normal file
View File

@ -0,0 +1,411 @@
# 🌐 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 |
---
🎯 **This guide ensures reliable, secure, and user-friendly Wi-Fi connectivity** for both **single-court clubs** and **multi-court tournaments**.
You now have everything needed to implement **auto-discovery** and **prevent cross-talk**.
📬 For help: Contact project lead.
```
---
### ✅ How to Use
1. Save as `WIFI_CONNECTIVITY_GUIDE.md`
2. Place in `/docs` folder
3. Commit to GitHub
---
Let me know if you want:
- A **diagram (Mermaid)** of the network flow
- A **PDF version**
- A **device configuration template**
- A **tournament setup checklist**
You're now fully equipped to build a **rock-solid, scalable Wi-Fi system**. 🏆