5.8 KiB
5.8 KiB
🧠 Taekwondo Scoring System – Spinning Kick Detection: Corrected Logic
Version: 1.1
Last Updated: July 2025
Target Developer: Embedded & Android Developers
Purpose: Fix the spinning detection logic — it must come from the attacker, not the defender
📌 Table of Contents
- 1. The Problem
- 2. Correct Logic: Spinning is Detected on the Attacker
- 3. Data Flow for Spinning Kick
- 4. Time Synchronization & Correlation
- 5. Updated Impact & Spinning Messages
- 6. Scoring Engine Logic (C#)
- 7. ESP32: MPU6050 on Hogu (Attacker Side)
- 8. Best Practices
1. The Problem
In the original design, spinning detection was tied to the impact sensor:
{"type":"impact","device":"head_blue","force":900,"spinning":true,"time":...}
But this is physically incorrect:
- When Red kicks Blue, Blue’s headgear detects the impact.
- But Red’s body is the one that spun.
- Blue’s sensor cannot know that Red was spinning.
❌ You cannot detect the attacker’s motion on the defender’s device.
2. Correct Logic: Spinning is Detected on the Attacker
✅ Spinning must be detected on the attacker’s hogu (trunk protector), where the MPU6050 is mounted.
The system must:
- Detect impact on defender’s sensor (e.g.,
head_blue) - Detect rotation > 180° on attacker’s sensor (e.g.,
hogu_red) - Confirm both events occurred within ±200ms
- Award 4 or 5 points only if both conditions are met
3. Data Flow for Spinning Kick
[Red Athlete]
↓
ESP32 in Hogu detects: "I am spinning!" → sends:
{"type":"motion","device":"hogu_red","spinning":true,"time":T1}
[Red kicks Blue]
↓
ESP32 in Blue's headgear detects: "I was hit!" → sends:
{"type":"impact","device":"head_blue","force":900,"time":T2}
[Score Control App]
↓
Checks:
- Was there a spinning event near T2?
- From same athlete (Red)?
- Within ±200ms?
→ If yes: Award 5 points
4. Time Synchronization & Correlation
All devices use UTC ticks for precise timing.
Correlation Window
bool IsSpinningKick(long impactTime, long spinningTime)
{
long window = 200 * TimeSpan.TicksPerMillisecond; // ±200ms
return Math.Abs(impactTime - spinningTime) <= window;
}
Determine Attacker from Device Name
hogu_red+head_blue→ Red attacked Blue- So check if Red was spinning at impact time
5. Updated Impact & Spinning Messages
5.1 Impact (Defender’s Device)
{
"type": "impact",
"device": "head_blue",
"force": 900,
"time": 1712345678901
}
❌ No
spinningfield here — irrelevant.
5.2 Motion (Attacker’s Device)
{
"type": "motion",
"device": "hogu_red",
"spinning": true,
"angle": 270,
"time": 1712345678850
}
Sent only when rotation > 180° is detected on attacker’s hogu.
6. Scoring Engine Logic (C#)
private List<MotionEvent> _motionEvents = new();
public void AddImpact(string device, int force, long time)
{
if (force < GetThreshold(device)) return;
string defender = device.Contains("_red") ? "red" : "blue";
string attacker = defender == "red" ? "blue" : "red";
// Find matching spinning event from attacker
var spinningEvent = _motionEvents
.Where(m => IsSameAthlete(m.Device, attacker) && IsInTimeWindow(m.Time, time))
.LastOrDefault();
bool isSpinning = spinningEvent != null;
int points = CalculatePoints(device, isSpinning);
if (points > 0)
{
AddPoints(attacker, points);
BroadcastScoreUpdate();
LogEvent($"✅ +{points} to {attacker} (Spinning: {isSpinning})");
}
}
public void AddMotion(string device, bool spinning, long time)
{
if (spinning)
{
_motionEvents.Add(new MotionEvent { Device = device, Time = time });
// Keep only last 5 sec of events
_motionEvents = _motionEvents.Where(m => m.Time > time - 50000000).ToList();
}
}
private bool IsSameAthlete(string device, string athlete)
{
return device.Contains($"_{athlete}");
}
private bool IsInTimeWindow(long spinningTime, long impactTime)
{
long diff = Math.Abs(impactTime - spinningTime);
return diff <= 200 * TimeSpan.TicksPerMillisecond; // ±200ms
}
private int CalculatePoints(string device, bool spinning)
{
if (device.Contains("head"))
return spinning ? 5 : 3;
else
return spinning ? 4 : 1;
}
// Data Class
public class MotionEvent { public string Device; public long Time; }
7. ESP32: MPU6050 on Hogu (Attacker Side)
📌 Mount MPU6050 on Hogu, not headgear
#include <Adafruit_MPU6050.h>
Adafruit_MPU6050 mpu;
void checkSpinning() {
sensors_event_t gyro;
mpu.getGyroSensor(&gyro);
float rotation = abs(gyro.gyro.z); // Z-axis = spin
if (rotation > 1.0) { // Threshold (rad/s)
long now = millis() * 10000; // Ticks
static long lastSpin = 0;
if (now - lastSpin > 20000000) { // 2 sec cooldown
lastSpin = now;
client.print("{\"type\":\"motion\",\"device\":\"hogu_red\",\"spinning\":true,\"time\":" + String(now) + "}\n");
}
}
}
Only hogu devices send
motionevents.
8. Best Practices
| Practice | Why |
|---|---|
Only hogu sends motion events |
Headgear can't detect attacker spin |
| Correlate within ±200ms | Fast kicks, timing jitter |
| Use UTC ticks | No drift |
| Clear old motion events | Prevent false matches |
| Log spinning events | Debugging |
| Calibrate MPU6050 | Reduce false positives |