takeone-event-managment/doc/hugo-sensors.md

228 lines
5.8 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 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](#1-the-problem)
- [2. Correct Logic: Spinning is Detected on the Attacker](#2-correct-logic-spinning-is-detected-on-the-attacker)
- [3. Data Flow for Spinning Kick](#3-data-flow-for-spinning-kick)
- [4. Time Synchronization & Correlation](#4-time-synchronization--correlation)
- [5. Updated Impact & Spinning Messages](#5-updated-impact--spinning-messages)
- [6. Scoring Engine Logic (C#)](#6-scoring-engine-logic-c)
- [7. ESP32: MPU6050 on Hogu (Attacker Side)](#7-esp32-mpu6050-on-hogu-attacker-side)
- [8. Best Practices](#8-best-practices)
---
## 1. The Problem
In the **original design**, spinning detection was tied to the **impact sensor**:
```json
{"type":"impact","device":"head_blue","force":900,"spinning":true,"time":...}
```
But this is **physically incorrect**:
- When **Red kicks Blue**, **Blues headgear** detects the impact.
- But **Reds body** is the one that spun.
- **Blues sensor cannot know** that **Red** was spinning.
> ❌ **You cannot detect the attackers motion on the defenders device.**
---
## 2. Correct Logic: Spinning is Detected on the Attacker
**Spinning must be detected on the attackers hogu (trunk protector)**, where the **MPU6050** is mounted.
The system must:
1. Detect **impact** on **defenders sensor** (e.g., `head_blue`)
2. Detect **rotation > 180°** on **attackers sensor** (e.g., `hogu_red`)
3. Confirm both events occurred within **±200ms**
4. 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
```csharp
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 (Defenders Device)
```json
{
"type": "impact",
"device": "head_blue",
"force": 900,
"time": 1712345678901
}
```
> ❌ **No `spinning` field here** — irrelevant.
### 5.2 Motion (Attackers Device)
```json
{
"type": "motion",
"device": "hogu_red",
"spinning": true,
"angle": 270,
"time": 1712345678850
}
```
> Sent only when **rotation > 180°** is detected on **attackers hogu**.
---
## 6. Scoring Engine Logic (C#)
```csharp
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
```cpp
#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 `motion` events.
---
## 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 |