Add doc/hugo-sensors.md

This commit is contained in:
Ghassan Yusuf 2025-07-23 15:42:24 +03:00
parent abb6312c61
commit 34bffeb565

226
doc/hugo-sensors.md Normal file
View 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**, **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 |