Add doc/hugo-sensors.md
This commit is contained in:
parent
abb6312c61
commit
34bffeb565
226
doc/hugo-sensors.md
Normal file
226
doc/hugo-sensors.md
Normal file
@ -0,0 +1,226 @@
|
||||
# 🧠 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**, **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:
|
||||
1. Detect **impact** on **defender’s sensor** (e.g., `head_blue`)
|
||||
2. Detect **rotation > 180°** on **attacker’s 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 (Defender’s Device)
|
||||
```json
|
||||
{
|
||||
"type": "impact",
|
||||
"device": "head_blue",
|
||||
"force": 900,
|
||||
"time": 1712345678901
|
||||
}
|
||||
```
|
||||
> ❌ **No `spinning` field here** — irrelevant.
|
||||
|
||||
### 5.2 Motion (Attacker’s Device)
|
||||
```json
|
||||
{
|
||||
"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#)
|
||||
|
||||
```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 |
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user