439 lines
10 KiB
Markdown
439 lines
10 KiB
Markdown
# 📡 Taekwondo Scoring System – Score Control to Display Communication
|
||
|
||
> **Version:** 1.0
|
||
> **Last Updated:** July 2025
|
||
> **Target Developer:** .NET Android (Score Control) & Android TV App Developers
|
||
> **Purpose:** Define **real-time data signals** from **Score Control Unit** to **Scoreboard** and **Upcoming Matches** displays
|
||
|
||
---
|
||
|
||
## 📌 Table of Contents
|
||
|
||
- [1. Overview](#1-overview)
|
||
- [2. System Architecture](#2-system-architecture)
|
||
- [3. Communication Protocol (TCP)](#3-communication-protocol-tcp)
|
||
- [4. Scoreboard (Live Match Display)](#4-scoreboard-live-match-display)
|
||
- [5. Upcoming Matches Screen](#5-upcoming-matches-screen)
|
||
- [6. Message Format (JSON)](#6-message-format-json)
|
||
- [7. C# Server Code (Score Control)](#7-c-server-code-score-control)
|
||
- [8. Android TV App Logic](#8-android-tv-app-logic)
|
||
- [9. Display Modes & Transitions](#9-display-modes--transitions)
|
||
- [10. Best Practices](#10-best-practices)
|
||
|
||
---
|
||
|
||
## 1. Overview
|
||
|
||
The **Score Control Unit (Android tablet)** sends **real-time data** to **two Android TVs**:
|
||
|
||
| Display | Purpose |
|
||
|--------|--------|
|
||
| **Scoreboard TV** | Live match: score, timer, fighter info |
|
||
| **Upcoming Matches TV** | Lobby: next matches, tournament flow |
|
||
|
||
All communication is:
|
||
- ✅ **Real-time**
|
||
- ✅ **Bidirectional-ready** (optional)
|
||
- ✅ **Over Wi-Fi (TCP)**
|
||
- ✅ **Zero-config for users**
|
||
|
||
---
|
||
|
||
## 2. System Architecture
|
||
|
||
```
|
||
[Score Control Tablet] → Wi-Fi → [Scoreboard TV (Port 5001)]
|
||
↘
|
||
[Upcoming Matches TV (Port 5002)]
|
||
```
|
||
|
||
- Score Control acts as **TCP server**
|
||
- TVs act as **TCP clients**
|
||
- TVs connect at startup and **stay connected**
|
||
- Score Control **broadcasts** JSON messages
|
||
|
||
> ✅ All devices on same network (`TKD_SCORING_COURT1`)
|
||
|
||
---
|
||
|
||
## 3. Communication Protocol (TCP)
|
||
|
||
### 🔌 Connection Flow
|
||
1. TV powers on → connects to `TKD_SCORING_COURT1`
|
||
2. TV app starts → connects to tablet IP (`192.168.4.1`)
|
||
3. TV sends identity:
|
||
```
|
||
DISPLAY:NAME=TV_SCOREBOARD,TYPE=SCOREBOARD,MAC=18:3D:A2:01:02:03
|
||
```
|
||
4. Score Control registers TV
|
||
5. TV listens for JSON messages
|
||
|
||
---
|
||
|
||
## 4. Scoreboard (Live Match Display)
|
||
|
||
Displays real-time match data.
|
||
|
||
### 🖼️ 13 Display Elements
|
||
| Element | Source |
|
||
|-------|--------|
|
||
| Court Number | Match data |
|
||
| Match Number | Schedule |
|
||
| Match Timer | App timer |
|
||
| Round Counter | Match state |
|
||
| Country Flag | Fighter data |
|
||
| Country ISO3 | Fighter data |
|
||
| Team Logo | Fighter data |
|
||
| Fighter Name | Match setup |
|
||
| Fighter Score | Scoring engine |
|
||
| Fighter Fouls | Penalty tracker |
|
||
| Fighter Weight (kg) | Fighter profile |
|
||
| Weight Class & Category | Match data |
|
||
| Team Name | Fighter data |
|
||
|
||
---
|
||
|
||
### 🔄 Display Modes
|
||
| Mode | Trigger | Duration |
|
||
|------|--------|---------|
|
||
| `faceoff` | Before match | 10 sec |
|
||
| `score` | Match start | 2:00 |
|
||
| `break` | Round end | 60 sec |
|
||
| `call_to_action` | Last 10 sec of break | 10 sec |
|
||
| `winner` | Match end | 15 sec |
|
||
|
||
---
|
||
|
||
## 5. Upcoming Matches Screen
|
||
|
||
Displays next 3–5 matches.
|
||
|
||
### 🖼️ Display Elements
|
||
- Match number
|
||
- Red vs Blue names
|
||
- Weight class
|
||
- Round
|
||
- Scheduled time
|
||
- Tournament logo
|
||
|
||
### 🔄 Auto-Advance
|
||
- Updates when current match ends
|
||
- Pulls from CSV or cloud
|
||
- Scrolls if more than 5 matches
|
||
|
||
---
|
||
|
||
## 6. Message Format (JSON)
|
||
|
||
All messages are **JSON** and end with `\n`.
|
||
|
||
---
|
||
|
||
### 6.1 To Scoreboard TV (Port 5001)
|
||
|
||
#### `faceoff` Mode
|
||
```json
|
||
{
|
||
"mode": "faceoff",
|
||
"red": {
|
||
"name": "Kim Min-jae",
|
||
"country": "KOR",
|
||
"flag": "https://flags/kor.png",
|
||
"photo": "https://photos/kim.jpg"
|
||
},
|
||
"blue": {
|
||
"name": "Lee Jung-ho",
|
||
"country": "CHN",
|
||
"flag": "https://flags/chn.png",
|
||
"photo": "https://photos/lee.jpg"
|
||
},
|
||
"weight_class": "Men's -68kg",
|
||
"round": "Semifinal"
|
||
}
|
||
```
|
||
|
||
#### `score` Mode
|
||
```json
|
||
{
|
||
"mode": "score",
|
||
"court": "1",
|
||
"match_number": "105",
|
||
"weight_class": "Men's -68kg",
|
||
"category": "Semifinal",
|
||
"round": "2",
|
||
"timer": "01:45",
|
||
"red": {
|
||
"name": "Kim Min-jae",
|
||
"country": "KOR",
|
||
"team": "Seoul TKD Club",
|
||
"logo": "https://logos/seoul.png",
|
||
"flag": "https://flags/kor.png",
|
||
"score": 8,
|
||
"fouls": 1,
|
||
"weight_kg": 67.2
|
||
},
|
||
"blue": {
|
||
"name": "Lee Jung-ho",
|
||
"country": "CHN",
|
||
"team": "Beijing TKD",
|
||
"logo": "https://logos/beijing.png",
|
||
"flag": "https://flags/chn.png",
|
||
"score": 5,
|
||
"fouls": 2,
|
||
"weight_kg": 67.8
|
||
}
|
||
}
|
||
```
|
||
|
||
#### `break` Mode
|
||
```json
|
||
{
|
||
"mode": "break",
|
||
"ad_image": "https://ads/sponsor1.png",
|
||
"highlight_video": "https://videos/round1.mp4",
|
||
"countdown": true
|
||
}
|
||
```
|
||
|
||
#### `call_to_action` Mode
|
||
```json
|
||
{
|
||
"mode": "call_to_action",
|
||
"message": "FIGHTERS, RETURN TO THE RING!",
|
||
"timer": 10
|
||
}
|
||
```
|
||
|
||
#### `winner` Mode
|
||
```json
|
||
{
|
||
"mode": "winner",
|
||
"winner": "red",
|
||
"red": {
|
||
"name": "Kim Min-jae",
|
||
"photo": "https://photos/kim.jpg",
|
||
"final_score": 8
|
||
},
|
||
"blue": {
|
||
"name": "Lee Jung-ho",
|
||
"photo": "https://photos/lee.jpg",
|
||
"final_score": 5
|
||
},
|
||
"weight_class": "Men's -68kg",
|
||
"round": "Final",
|
||
"animation": "fireworks"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 6.2 To Upcoming Matches TV (Port 5002)
|
||
|
||
```json
|
||
{
|
||
"mode": "next_matches",
|
||
"matches": [
|
||
{
|
||
"match_number": "106",
|
||
"red": "Park S.",
|
||
"blue": "Wang L.",
|
||
"weight": "+87kg",
|
||
"round": "Final",
|
||
"time": "11:30",
|
||
"category": "Men's"
|
||
},
|
||
{
|
||
"match_number": "107",
|
||
"red": "Choi M.",
|
||
"blue": "Tanaka Y.",
|
||
"weight": "-58kg",
|
||
"round": "Semifinal",
|
||
"time": "11:50",
|
||
"category": "Women's"
|
||
}
|
||
],
|
||
"tournament_name": "National Taekwondo Championship 2025",
|
||
"logo": "https://logos/tournament.png"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 7. C# Server Code (Score Control)
|
||
|
||
```csharp
|
||
public class DisplayServer
|
||
{
|
||
private TcpListener _scoreboardServer;
|
||
private TcpListener _matchesServer;
|
||
private List<TcpClient> _scoreboardClients = new();
|
||
private List<TcpClient> _matchesClients = new();
|
||
|
||
public async Task Start()
|
||
{
|
||
_scoreboardServer = new TcpListener(IPAddress.Any, 5001);
|
||
_matchesServer = new TcpListener(IPAddress.Any, 5002);
|
||
|
||
_scoreboardServer.Start();
|
||
_matchesServer.Start();
|
||
|
||
_ = AcceptScoreboardClients();
|
||
_ = AcceptMatchesClients();
|
||
}
|
||
|
||
private async Task AcceptScoreboardClients()
|
||
{
|
||
while (true)
|
||
{
|
||
var client = await _scoreboardServer.AcceptTcpClientAsync();
|
||
_scoreboardClients.Add(client);
|
||
_ = HandleDisplayClient(client, "SCOREBOARD");
|
||
}
|
||
}
|
||
|
||
private async Task AcceptMatchesClients()
|
||
{
|
||
while (true)
|
||
{
|
||
var client = await _matchesServer.AcceptTcpClientAsync();
|
||
_matchesClients.Add(client);
|
||
_ = HandleDisplayClient(client, "MATCHES");
|
||
}
|
||
}
|
||
|
||
private async Task HandleDisplayClient(TcpClient client, string type)
|
||
{
|
||
using var stream = client.GetStream();
|
||
using var reader = new StreamReader(stream);
|
||
|
||
string identity = await reader.ReadLineAsync();
|
||
// Validate: DISPLAY:NAME=...,TYPE=...,MAC=...
|
||
|
||
// Keep connection open
|
||
while (client.Connected)
|
||
{
|
||
await Task.Delay(1000); // Wait for broadcast
|
||
}
|
||
|
||
// Remove on disconnect
|
||
if (type == "SCOREBOARD")
|
||
_scoreboardClients.Remove(client);
|
||
else
|
||
_matchesClients.Remove(client);
|
||
}
|
||
|
||
public void BroadcastToScoreboard(object data)
|
||
{
|
||
string json = JsonSerializer.Serialize(data) + "\n";
|
||
byte[] bytes = Encoding.UTF8.GetBytes(json);
|
||
|
||
for (int i = _scoreboardClients.Count - 1; i >= 0; i--)
|
||
{
|
||
var client = _scoreboardClients[i];
|
||
if (client?.Connected == true)
|
||
{
|
||
try
|
||
{
|
||
client.GetStream().Write(bytes, 0, bytes.Length);
|
||
}
|
||
catch
|
||
{
|
||
_scoreboardClients.RemoveAt(i); // Cleanup
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public void BroadcastToMatches(object data)
|
||
{
|
||
string json = JsonSerializer.Serialize(data) + "\n";
|
||
byte[] bytes = Encoding.UTF8.GetBytes(json);
|
||
|
||
for (int i = _matchesClients.Count - 1; i >= 0; i--)
|
||
{
|
||
var client = _matchesClients[i];
|
||
if (client?.Connected == true)
|
||
{
|
||
try
|
||
{
|
||
client.GetStream().Write(bytes, 0, bytes.Length);
|
||
}
|
||
catch
|
||
{
|
||
_matchesClients.RemoveAt(i);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Android TV App Logic
|
||
|
||
### 📺 Scoreboard TV App
|
||
- Connects to `192.168.4.1:5001`
|
||
- Sends identity
|
||
- Listens for JSON
|
||
- Renders based on `mode`
|
||
- Supports video playback for `highlight_video`
|
||
|
||
### 📺 Upcoming Matches TV App
|
||
- Connects to `192.168.4.1:5002`
|
||
- Displays scrollable list
|
||
- Updates on new message
|
||
- Shows tournament logo
|
||
|
||
---
|
||
|
||
## 9. Display Modes & Transitions
|
||
|
||
| Action | Broadcast To | Mode |
|
||
|-------|--------------|------|
|
||
| Match selected | Scoreboard | `faceoff` |
|
||
| "Shi-jak" pressed | Scoreboard | `score` |
|
||
| Round ends | Scoreboard | `break` |
|
||
| 10 sec before next round | Scoreboard | `call_to_action` |
|
||
| Match ends | Scoreboard | `winner` |
|
||
| Match ends | Upcoming Matches | `next_matches` |
|
||
| Tournament starts | Upcoming Matches | `next_matches` |
|
||
|
||
---
|
||
|
||
## 10. Best Practices
|
||
|
||
| Practice | Why |
|
||
|--------|-----|
|
||
| Use separate ports (5001, 5002) | Avoid mode conflicts |
|
||
| Send `\n` after each message | Easy parsing |
|
||
| Validate device identity | Security |
|
||
| Reconnect logic on TV | Handle Wi-Fi drops |
|
||
| Broadcast only on change | Reduce network load |
|
||
| Use UTC time in logs | Debugging |
|
||
| Include `mode` in every message | Safe rendering |
|
||
|
||
---
|
||
|
||
🎯 **This document fully defines the communication between Score Control and Displays.**
|
||
You now have everything to build **real-time, synchronized, broadcast-quality displays**.
|
||
📬 For help: Contact project lead.
|
||
```
|
||
|
||
---
|
||
|
||
### ✅ How to Use
|
||
|
||
1. Save as `DISPLAY_COMMUNICATION.md`
|
||
2. Place in `/docs` folder
|
||
3. Commit to GitHub
|
||
|
||
---
|
||
|
||
Let me know if you want:
|
||
- A **Figma mockup** of the TV screens
|
||
- A **sample Android TV app** (Kotlin)
|
||
- A **test tool** to simulate messages
|
||
- A **Lottie animation** for the winner screen
|
||
|
||
You're now building a **fully integrated, professional tournament system**. 🏆 |