Compare commits
8 Commits
d6f928ec31
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f750378e9d | |||
| 45bc779556 | |||
| f825695658 | |||
| f33fd5b216 | |||
| f23ddf9a82 | |||
| 3bb8d6c58a | |||
| 6048ed9066 | |||
| ad72c2aad5 |
30
.claude/settings.local.json
Normal file
30
.claude/settings.local.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(powershell -Command \"Get-ChildItem ''C:\\\\Program Files'',''C:\\\\Program Files \\(x86\\)'',''C:\\\\Arduino'',''D:\\\\'' -Filter ''arduino.exe'' -Recurse -Depth 3 -ErrorAction SilentlyContinue | Select -First 3 -ExpandProperty FullName\")",
|
||||||
|
"Bash(\"/c/Program Files/Epic Games/UE_5.5/Engine/Build/BatchFiles/Build.bat\" PS_BLE_Tracker Win64 Development -project=\"C:/ASTERION/GIT/PS_BLE_Tracker/Unreal/PS_BLE_Tracker.uproject\" -waitmutex)",
|
||||||
|
"Bash(powershell -Command \"\\(Get-ItemProperty ''HKLM:\\\\SOFTWARE\\\\WOW6432Node\\\\Arduino'' -ErrorAction SilentlyContinue\\).Install_Dir; \\(Get-ItemProperty ''HKLM:\\\\SOFTWARE\\\\Arduino'' -ErrorAction SilentlyContinue\\).Install_Dir; \\(Get-ItemProperty ''HKCU:\\\\SOFTWARE\\\\Arduino'' -ErrorAction SilentlyContinue\\).Install_Dir\")",
|
||||||
|
"Bash(powershell -Command \"Get-Command arduino -ErrorAction SilentlyContinue | Select -ExpandProperty Source; Get-ItemProperty ''HKLM:\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\*'',''HKLM:\\\\SOFTWARE\\\\WOW6432Node\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\*'' -ErrorAction SilentlyContinue | Where-Object DisplayName -like ''*Arduino*'' | Select DisplayName,InstallLocation | Format-List\")",
|
||||||
|
"Bash(powershell -Command \"Get-ChildItem ''$env:APPDATA\\\\Microsoft\\\\Windows\\\\Start Menu'' -Filter ''*arduino*'' -Recurse -ErrorAction SilentlyContinue | Select FullName; Get-ChildItem ''C:\\\\ProgramData\\\\Microsoft\\\\Windows\\\\Start Menu'' -Filter ''*arduino*'' -Recurse -ErrorAction SilentlyContinue | Select FullName; Get-ChildItem ''$env:USERPROFILE\\\\Desktop'' -Filter ''*arduino*'' -ErrorAction SilentlyContinue | Select FullName\")",
|
||||||
|
"Read(//c/Users/j_foucher/AppData/Local/Arduino15/**)",
|
||||||
|
"Read(//c/Users/j_foucher/AppData/Roaming/Arduino15/**)",
|
||||||
|
"Bash(powershell -Command \"Get-ChildItem ''C:\\\\'' -Directory -ErrorAction SilentlyContinue | ForEach-Object { Get-ChildItem $_FullName -Filter ''arduino.exe'' -ErrorAction SilentlyContinue -Depth 2 } | Select -First 3 -ExpandProperty FullName\")",
|
||||||
|
"Bash(powershell -Command \"Get-ChildItem ''C:\\\\Program Files'',''C:\\\\Program Files \\(x86\\)'',''C:\\\\Users\\\\j_foucher'' -Directory -ErrorAction SilentlyContinue | ForEach-Object { Get-ChildItem $_FullName -Filter ''arduino.exe'' -Recurse -Depth 3 -ErrorAction SilentlyContinue } | Select -First 5 -ExpandProperty FullName\")",
|
||||||
|
"Bash(powershell -Command \"Get-Process | Where-Object { $_.ProcessName -match ''arduino|java'' -and $_.Path } | Select ProcessName,Path | Format-List\")",
|
||||||
|
"Read(//c/Users/j_foucher/AppData/Local/**)",
|
||||||
|
"Bash(find /c/Users/j_foucher -maxdepth 6 -name boards.txt -path *nrf*)",
|
||||||
|
"Bash(ARDUINO_DIR=\"/c/Program Files/WindowsApps/ArduinoLLC.ArduinoIDE_1.8.57.0_x86__mdqgnx93n4wtt\")",
|
||||||
|
"Bash(\"$ARDUINO_DIR/arduino-builder\" -hardware \"$ARDUINO_DIR/hardware\" -hardware \"/c/Users/j_foucher/Documents/ArduinoData/packages\" -tools \"$ARDUINO_DIR/tools-builder\" -tools \"/c/Users/j_foucher/Documents/ArduinoData/packages\" -libraries \"/c/Users/j_foucher/Documents/Arduino/libraries\" -fqbn \"Seeeduino:mbed:xiaonRF52840Sense\" -build-path \"/c/Users/j_foucher/AppData/Local/Temp/arduino_build\" -build-cache \"/c/Users/j_foucher/AppData/Local/Temp/arduino_cache\" -verbose \"C:/ASTERION/GIT/PS_BLE_Tracker/Arduino/PS_BLE_ShotDetection/PS_BLE_ShotDetection.ino\")",
|
||||||
|
"Bash(mkdir -p /c/Users/j_foucher/AppData/Local/Temp/arduino_build)",
|
||||||
|
"Bash(cmd.exe //c \"\"\"C:\\\\Program Files\\\\WindowsApps\\\\ArduinoLLC.ArduinoIDE_1.8.57.0_x86__mdqgnx93n4wtt\\\\arduino_debug.exe\"\" --verify --board Seeeduino:mbed:xiaonRF52840Sense --pref build.path=C:\\\\Users\\\\j_foucher\\\\AppData\\\\Local\\\\Temp\\\\arduino_build \"\"C:\\\\ASTERION\\\\GIT\\\\PS_BLE_Tracker\\\\Arduino\\\\PS_BLE_ShotDetection\\\\PS_BLE_ShotDetection.ino\"\" 2>&1\")",
|
||||||
|
"Bash(powershell -Command \"& ''C:\\\\Program Files\\\\WindowsApps\\\\ArduinoLLC.ArduinoIDE_1.8.57.0_x86__mdqgnx93n4wtt\\\\arduino_debug.exe'' --verify --board Seeeduino:mbed:xiaonRF52840Sense --pref ''build.path=C:\\\\Users\\\\j_foucher\\\\AppData\\\\Local\\\\Temp\\\\arduino_build'' ''C:\\\\ASTERION\\\\GIT\\\\PS_BLE_Tracker\\\\Arduino\\\\PS_BLE_ShotDetection\\\\PS_BLE_ShotDetection.ino'' 2>&1\")",
|
||||||
|
"Bash(where winget:*)",
|
||||||
|
"Bash(winget install:*)",
|
||||||
|
"Bash(export PATH=\"$PATH:/c/Program Files/Arduino CLI\")",
|
||||||
|
"Bash(arduino-cli version:*)",
|
||||||
|
"Bash(arduino-cli core:*)",
|
||||||
|
"Bash(arduino-cli lib:*)",
|
||||||
|
"Bash(arduino-cli compile:*)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,3 +3,5 @@ Unreal/Intermediate/
|
|||||||
Unreal/Plugins/WinBluetoothLE/Intermediate/
|
Unreal/Plugins/WinBluetoothLE/Intermediate/
|
||||||
Unreal/Binaries/
|
Unreal/Binaries/
|
||||||
Unreal/Plugins/WinBluetoothLE/Binaries/
|
Unreal/Plugins/WinBluetoothLE/Binaries/
|
||||||
|
Unreal/Plugins/PS_Win_BLE/Binaries/
|
||||||
|
Unreal/Plugins/PS_Win_BLE/Intermediate/
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from collections import deque
|
|||||||
from bleak import BleakClient, BleakScanner
|
from bleak import BleakClient, BleakScanner
|
||||||
|
|
||||||
DEVICE_NAME = "XIAO Airsoft Pro"
|
DEVICE_NAME = "XIAO Airsoft Pro"
|
||||||
DEVICE_ADDRESS = "" # Laisser vide pour scan automatique par nom, ou mettre l'adresse MAC pour connexion directe
|
DEVICE_ADDRESS = "46:35:F1:51:51:5A" # Adresse MAC directe — plus fiable que le scan par nom
|
||||||
DEBUG_CHAR_UUID = "6E400005-B5A3-F393-E0A9-E50E24DCCA9E"
|
DEBUG_CHAR_UUID = "6E400005-B5A3-F393-E0A9-E50E24DCCA9E"
|
||||||
SHOT_CHAR_UUID = "6E400004-B5A3-F393-E0A9-E50E24DCCA9E"
|
SHOT_CHAR_UUID = "6E400004-B5A3-F393-E0A9-E50E24DCCA9E"
|
||||||
CONFIG_CHAR_UUID = "6E400006-B5A3-F393-E0A9-E50E24DCCA9E"
|
CONFIG_CHAR_UUID = "6E400006-B5A3-F393-E0A9-E50E24DCCA9E"
|
||||||
@@ -34,7 +34,9 @@ thresholds = {"accel": 2.5, "gyro": 200.0, "audio": 3000} # PDM : 0-32767
|
|||||||
min_sensors = 3 # Nb de capteurs requis simultanément (1-3) — NUM6=+1 NUM4=-1
|
min_sensors = 3 # Nb de capteurs requis simultanément (1-3) — NUM6=+1 NUM4=-1
|
||||||
shot_count = 0
|
shot_count = 0
|
||||||
shot_pending = False # Flag : un tir reçu, à enregistrer dans shot_buf au prochain debug tick
|
shot_pending = False # Flag : un tir reçu, à enregistrer dans shot_buf au prochain debug tick
|
||||||
ble_status = "🔍 Connexion..."
|
# Pic max au moment du dernier tir (mis à jour au moment où shot_pending devient True)
|
||||||
|
last_shot_peak = {"accel": 0.0, "gyro": 0.0, "audio": 0}
|
||||||
|
ble_status = "[..] Connexion..."
|
||||||
ble_running = True
|
ble_running = True
|
||||||
audio_max_global = 1000 # Tracks le max absolu jamais vu
|
audio_max_global = 1000 # Tracks le max absolu jamais vu
|
||||||
ble_client = None # Référence au client BLE actif
|
ble_client = None # Référence au client BLE actif
|
||||||
@@ -44,6 +46,9 @@ debug_mode = 3 # 0=OFF, 1=RAW, 2=TRIGGERS, 3=FULL (actif par défaut)
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
X = np.arange(WINDOW_SIZE) # axe X fixe, ne change jamais
|
X = np.arange(WINDOW_SIZE) # axe X fixe, ne change jamais
|
||||||
|
|
||||||
|
# Fenêtre glissante pour afficher le pic max récent dans le titre (informatif seulement)
|
||||||
|
PEAK_WINDOW = 20 # ~1s à 20Hz
|
||||||
|
|
||||||
# ─── BLE ────────────────────────────────────────────────
|
# ─── BLE ────────────────────────────────────────────────
|
||||||
def debug_callback(sender, data):
|
def debug_callback(sender, data):
|
||||||
global audio_max_global, shot_pending
|
global audio_max_global, shot_pending
|
||||||
@@ -63,10 +68,14 @@ def debug_callback(sender, data):
|
|||||||
shot_pending = False
|
shot_pending = False
|
||||||
|
|
||||||
def shot_callback(sender, data):
|
def shot_callback(sender, data):
|
||||||
global shot_count, shot_pending
|
global shot_count, shot_pending, last_shot_peak
|
||||||
if data[0] == 1:
|
if data[0] == 1:
|
||||||
shot_count += 1
|
shot_count += 1
|
||||||
shot_pending = True
|
shot_pending = True
|
||||||
|
# Capturer le pic courant au moment exact du tir (meilleure approximation possible en BLE)
|
||||||
|
last_shot_peak["accel"] = accel_buf[-1] if accel_buf else 0.0
|
||||||
|
last_shot_peak["gyro"] = gyro_buf[-1] if gyro_buf else 0.0
|
||||||
|
last_shot_peak["audio"] = audio_buf[-1] if audio_buf else 0
|
||||||
|
|
||||||
async def find_device():
|
async def find_device():
|
||||||
"""Scan par nom (toutes les 0.5s pendant 30s max).
|
"""Scan par nom (toutes les 0.5s pendant 30s max).
|
||||||
@@ -122,10 +131,10 @@ async def ble_loop():
|
|||||||
global ble_status, ble_running, ble_client, config_pending
|
global ble_status, ble_running, ble_client, config_pending
|
||||||
while ble_running:
|
while ble_running:
|
||||||
try:
|
try:
|
||||||
ble_status = f"🔍 Recherche '{DEVICE_NAME}'..."
|
ble_status = f"[?] Recherche '{DEVICE_NAME}'..."
|
||||||
device = await find_device()
|
device = await find_device()
|
||||||
if not device:
|
if not device:
|
||||||
ble_status = "⚠️ XIAO non trouvé — réessai..."
|
ble_status = "[!] XIAO non trouve — reessai..."
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
continue
|
continue
|
||||||
# device peut être une string (adresse directe) ou un BLEDevice
|
# device peut être une string (adresse directe) ou un BLEDevice
|
||||||
@@ -135,13 +144,13 @@ async def ble_loop():
|
|||||||
else:
|
else:
|
||||||
addr = device.address
|
addr = device.address
|
||||||
display_name = device.name or DEVICE_NAME
|
display_name = device.name or DEVICE_NAME
|
||||||
ble_status = f"📡 Connexion {display_name}..."
|
ble_status = f"[..] Connexion {display_name}..."
|
||||||
|
|
||||||
async with BleakClient(addr, timeout=15.0) as client:
|
async with BleakClient(addr, timeout=15.0) as client:
|
||||||
ble_client = client
|
ble_client = client
|
||||||
await client.start_notify(DEBUG_CHAR_UUID, debug_callback)
|
await client.start_notify(DEBUG_CHAR_UUID, debug_callback)
|
||||||
await client.start_notify(SHOT_CHAR_UUID, shot_callback)
|
await client.start_notify(SHOT_CHAR_UUID, shot_callback)
|
||||||
ble_status = f"✅ {display_name} ({addr})"
|
ble_status = f"[OK] {display_name} ({addr})"
|
||||||
# À la connexion : lire la config sauvegardée dans le XIAO (flash)
|
# À la connexion : lire la config sauvegardée dans le XIAO (flash)
|
||||||
try:
|
try:
|
||||||
raw = await client.read_gatt_char(CONFIG_CHAR_UUID)
|
raw = await client.read_gatt_char(CONFIG_CHAR_UUID)
|
||||||
@@ -173,10 +182,14 @@ async def ble_loop():
|
|||||||
config_pending = False
|
config_pending = False
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
ble_client = None
|
ble_client = None
|
||||||
ble_status = "❌ Déconnecté — reconnexion..."
|
ble_status = "[X] Deconnecte — reconnexion..."
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
ble_client = None
|
||||||
|
ble_status = "[!] Connexion annulee (device deja connecte?) — reessai..."
|
||||||
|
await asyncio.sleep(3)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
ble_client = None
|
ble_client = None
|
||||||
ble_status = f"❌ {str(e)[:50]}"
|
ble_status = f"[X] {str(e)[:50]}"
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
def run_ble():
|
def run_ble():
|
||||||
@@ -192,9 +205,9 @@ from matplotlib.animation import FuncAnimation
|
|||||||
BG = '#1a1a2e'
|
BG = '#1a1a2e'
|
||||||
PANEL = '#16213e'
|
PANEL = '#16213e'
|
||||||
TEXT = '#e0e0e0'
|
TEXT = '#e0e0e0'
|
||||||
CA = '#4fc3f7' # accel
|
CA = '#4fc3f7' # accel brut
|
||||||
CG = '#81c784' # gyro
|
CG = '#81c784' # gyro brut
|
||||||
CM = '#ce93d8' # audio
|
CM = '#ce93d8' # audio brut
|
||||||
CT = '#ef5350' # trigger / seuil
|
CT = '#ef5350' # trigger / seuil
|
||||||
CS = '#ff9800' # shot (orange vif)
|
CS = '#ff9800' # shot (orange vif)
|
||||||
GRID = '#2a2a4a'
|
GRID = '#2a2a4a'
|
||||||
@@ -222,15 +235,23 @@ axes[3].set_ylim(-0.1, 1.5)
|
|||||||
axes[3].set_yticks([]) # pas de graduations Y sur la timeline
|
axes[3].set_yticks([]) # pas de graduations Y sur la timeline
|
||||||
|
|
||||||
# Créer les artistes UNE SEULE FOIS — on ne les recrée jamais
|
# Créer les artistes UNE SEULE FOIS — on ne les recrée jamais
|
||||||
line_a, = axes[0].plot(X, list(accel_buf), color=CA, lw=1.5)
|
line_a, = axes[0].plot(X, list(accel_buf), color=CA, lw=1.5, label='Valeur brute')
|
||||||
line_g, = axes[1].plot(X, list(gyro_buf), color=CG, lw=1.5)
|
line_g, = axes[1].plot(X, list(gyro_buf), color=CG, lw=1.5, label='Valeur brute')
|
||||||
line_m, = axes[2].plot(X, list(audio_buf), color=CM, lw=1.5)
|
line_m, = axes[2].plot(X, list(audio_buf), color=CM, lw=1.5, label='Valeur brute')
|
||||||
line_s, = axes[3].plot(X, list(shot_buf), color=CS, lw=0, marker='|',
|
line_s, = axes[3].plot(X, list(shot_buf), color=CS, lw=0, marker='|',
|
||||||
markersize=20, markeredgewidth=2.5) # barres verticales
|
markersize=20, markeredgewidth=2.5)
|
||||||
|
|
||||||
thr_a = axes[0].axhline(thresholds["accel"], color=CT, ls='--', lw=1.5)
|
thr_a = axes[0].axhline(thresholds["accel"], color=CT, ls='--', lw=1.5, label='Seuil (trigger XIAO)')
|
||||||
thr_g = axes[1].axhline(thresholds["gyro"], color=CT, ls='--', lw=1.5)
|
thr_g = axes[1].axhline(thresholds["gyro"], color=CT, ls='--', lw=1.5, label='Seuil (trigger XIAO)')
|
||||||
thr_m = axes[2].axhline(thresholds["audio"], color=CT, ls='--', lw=1.5)
|
thr_m = axes[2].axhline(thresholds["audio"], color=CT, ls='--', lw=1.5, label='Seuil (trigger XIAO)')
|
||||||
|
|
||||||
|
# Légendes
|
||||||
|
for ax in axes[:3]:
|
||||||
|
lines = ax.get_lines()
|
||||||
|
ax.legend(loc='upper left', fontsize=7, facecolor=PANEL, edgecolor='#444466',
|
||||||
|
labelcolor=TEXT, framealpha=0.8,
|
||||||
|
handles=[lines[0],
|
||||||
|
plt.Rectangle((0,0),1,1, fc=CT, alpha=0.25, label='Trigger actif (XIAO)')])
|
||||||
|
|
||||||
from matplotlib.patches import Rectangle
|
from matplotlib.patches import Rectangle
|
||||||
from matplotlib.collections import PatchCollection
|
from matplotlib.collections import PatchCollection
|
||||||
@@ -264,12 +285,12 @@ def update_spans(spans, trig_list):
|
|||||||
|
|
||||||
def update(frame):
|
def update(frame):
|
||||||
# Snapshot des buffers (rapide)
|
# Snapshot des buffers (rapide)
|
||||||
a = np.array(accel_buf)
|
a = np.array(accel_buf)
|
||||||
g = np.array(gyro_buf)
|
g = np.array(gyro_buf)
|
||||||
m = np.array(audio_buf)
|
m = np.array(audio_buf, dtype=float)
|
||||||
s = np.array(shot_buf)
|
s = np.array(shot_buf)
|
||||||
|
|
||||||
# Mise à jour des données des lignes
|
# Mise à jour des données des lignes brutes
|
||||||
line_a.set_ydata(a)
|
line_a.set_ydata(a)
|
||||||
line_g.set_ydata(g)
|
line_g.set_ydata(g)
|
||||||
line_m.set_ydata(m)
|
line_m.set_ydata(m)
|
||||||
@@ -296,21 +317,9 @@ def update(frame):
|
|||||||
span.set_alpha(0.85 if i < len(sl) and sl[i] > 0 else 0)
|
span.set_alpha(0.85 if i < len(sl) and sl[i] > 0 else 0)
|
||||||
|
|
||||||
# Titres avec valeurs courantes
|
# Titres avec valeurs courantes
|
||||||
titles[0].set_text(
|
titles[0].set_text(f"Accelerometre val: {a[-1]:.2f} G seuil: {thresholds['accel']:.1f} G [NUM7=+0.1 NUM1=-0.1]")
|
||||||
f"Accelerometre "
|
titles[1].set_text(f"Gyroscope val: {g[-1]:.0f} dps seuil: {thresholds['gyro']:.0f} dps [NUM8=+10 NUM2=-10]")
|
||||||
f"val: {a[-1]:.2f} G "
|
titles[2].set_text(f"Microphone PDM val: {m[-1]:.0f} seuil: {thresholds['audio']} [NUM9=+500 NUM3=-500]")
|
||||||
f"seuil: {thresholds['accel']:.1f} G "
|
|
||||||
f"[NUM7=+0.1 NUM1=-0.1]")
|
|
||||||
titles[1].set_text(
|
|
||||||
f"Gyroscope "
|
|
||||||
f"val: {g[-1]:.0f} dps "
|
|
||||||
f"seuil: {thresholds['gyro']:.0f} dps "
|
|
||||||
f"[NUM8=+10 NUM2=-10]")
|
|
||||||
titles[2].set_text(
|
|
||||||
f"Microphone PDM "
|
|
||||||
f"val: {m[-1]:.0f} "
|
|
||||||
f"seuil: {thresholds['audio']} "
|
|
||||||
f"[NUM9=+500 NUM3=-500]")
|
|
||||||
titles[3].set_text(f"Tirs detectes : {shot_count} (fenetre glissante)")
|
titles[3].set_text(f"Tirs detectes : {shot_count} (fenetre glissante)")
|
||||||
|
|
||||||
status_txt.set_text(
|
status_txt.set_text(
|
||||||
|
|||||||
8
Unreal/Plugins/PS_Win_BLE/Config/FilterPlugin.ini
Normal file
8
Unreal/Plugins/PS_Win_BLE/Config/FilterPlugin.ini
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[FilterPlugin]
|
||||||
|
; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and
|
||||||
|
; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively.
|
||||||
|
;
|
||||||
|
; Examples:
|
||||||
|
; /README.txt
|
||||||
|
; /Extras/...
|
||||||
|
; /Binaries/ThirdParty/*.dll
|
||||||
Binary file not shown.
2
remove worktree.txt
Normal file
2
remove worktree.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Supprimer E:\ASTERION\GIT\PS_BLE_Tracker\.claude\worktrees\hardcore-taussig
|
||||||
|
git worktree prune dans le repo
|
||||||
Reference in New Issue
Block a user