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

5.8 KiB
Raw Blame History

🧠 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

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, 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

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_blueRed attacked Blue
  • So check if Red was spinning at impact time

5. Updated Impact & Spinning Messages

5.1 Impact (Defenders Device)

{
  "type": "impact",
  "device": "head_blue",
  "force": 900,
  "time": 1712345678901
}

No spinning field here — irrelevant.

5.2 Motion (Attackers Device)

{
  "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#)

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 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