Python calibration tool: simplify graphs, fix CancelledError
- Remove orange "last shot peak" line (unreliable at 20Hz) - Remove rolling max curve (not useful in practice) - Add specific asyncio.CancelledError catch in ble_loop with retry message when device is already connected elsewhere - Clean up titles: show only current val + threshold Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f33fd5b216
commit
f825695658
@ -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"
|
||||||
@ -36,7 +36,7 @@ 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
|
||||||
# Pic max au moment du dernier tir (mis à jour au moment où shot_pending devient True)
|
# 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}
|
last_shot_peak = {"accel": 0.0, "gyro": 0.0, "audio": 0}
|
||||||
ble_status = "🔍 Connexion..."
|
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
|
||||||
@ -131,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
|
||||||
@ -144,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)
|
||||||
@ -182,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():
|
||||||
@ -241,16 +245,12 @@ thr_a = axes[0].axhline(thresholds["accel"], color=CT, ls='--', lw=1.5, label='S
|
|||||||
thr_g = axes[1].axhline(thresholds["gyro"], color=CT, ls='--', lw=1.5, label='Seuil (trigger XIAO)')
|
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, label='Seuil (trigger XIAO)')
|
thr_m = axes[2].axhline(thresholds["audio"], color=CT, ls='--', lw=1.5, label='Seuil (trigger XIAO)')
|
||||||
|
|
||||||
# Ligne horizontale orange = pic au moment du dernier tir (invisible au départ)
|
|
||||||
peak_a = axes[0].axhline(0, color=CS, ls='-', lw=1.5, alpha=0.0, label='Pic dernier tir')
|
|
||||||
peak_g = axes[1].axhline(0, color=CS, ls='-', lw=1.5, alpha=0.0, label='Pic dernier tir')
|
|
||||||
peak_m = axes[2].axhline(0, color=CS, ls='-', lw=1.5, alpha=0.0, label='Pic dernier tir')
|
|
||||||
|
|
||||||
# Légendes
|
# Légendes
|
||||||
for ax in axes[:3]:
|
for ax in axes[:3]:
|
||||||
|
lines = ax.get_lines()
|
||||||
ax.legend(loc='upper left', fontsize=7, facecolor=PANEL, edgecolor='#444466',
|
ax.legend(loc='upper left', fontsize=7, facecolor=PANEL, edgecolor='#444466',
|
||||||
labelcolor=TEXT, framealpha=0.8,
|
labelcolor=TEXT, framealpha=0.8,
|
||||||
handles=[ax.get_lines()[0], ax.get_lines()[1], ax.get_lines()[2],
|
handles=[lines[0],
|
||||||
plt.Rectangle((0,0),1,1, fc=CT, alpha=0.25, label='Trigger actif (XIAO)')])
|
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
|
||||||
@ -296,19 +296,6 @@ def update(frame):
|
|||||||
line_m.set_ydata(m)
|
line_m.set_ydata(m)
|
||||||
line_s.set_ydata(s)
|
line_s.set_ydata(s)
|
||||||
|
|
||||||
# Ligne orange = pic au moment du dernier tir (visible seulement si un tir a eu lieu)
|
|
||||||
if shot_count > 0:
|
|
||||||
peak_a.set_ydata([last_shot_peak["accel"]] * 2)
|
|
||||||
peak_g.set_ydata([last_shot_peak["gyro"]] * 2)
|
|
||||||
peak_m.set_ydata([last_shot_peak["audio"]] * 2)
|
|
||||||
peak_a.set_alpha(0.9)
|
|
||||||
peak_g.set_alpha(0.9)
|
|
||||||
peak_m.set_alpha(0.9)
|
|
||||||
else:
|
|
||||||
peak_a.set_alpha(0.0)
|
|
||||||
peak_g.set_alpha(0.0)
|
|
||||||
peak_m.set_alpha(0.0)
|
|
||||||
|
|
||||||
# Mise à jour des seuils
|
# Mise à jour des seuils
|
||||||
thr_a.set_ydata([thresholds["accel"], thresholds["accel"]])
|
thr_a.set_ydata([thresholds["accel"], thresholds["accel"]])
|
||||||
thr_g.set_ydata([thresholds["gyro"], thresholds["gyro"]])
|
thr_g.set_ydata([thresholds["gyro"], thresholds["gyro"]])
|
||||||
@ -329,22 +316,10 @@ def update(frame):
|
|||||||
for i, span in enumerate(shot_spans):
|
for i, span in enumerate(shot_spans):
|
||||||
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 + pic dernier tir
|
# Titres avec valeurs courantes
|
||||||
pa = last_shot_peak["accel"] if shot_count > 0 else None
|
titles[0].set_text(f"Accelerometre val: {a[-1]:.2f} G seuil: {thresholds['accel']:.1f} G [NUM7=+0.1 NUM1=-0.1]")
|
||||||
pg = last_shot_peak["gyro"] if shot_count > 0 else None
|
titles[1].set_text(f"Gyroscope val: {g[-1]:.0f} dps seuil: {thresholds['gyro']:.0f} dps [NUM8=+10 NUM2=-10]")
|
||||||
pm = last_shot_peak["audio"] if shot_count > 0 else None
|
titles[2].set_text(f"Microphone PDM val: {m[-1]:.0f} seuil: {thresholds['audio']} [NUM9=+500 NUM3=-500]")
|
||||||
titles[0].set_text(
|
|
||||||
f"Accelerometre val: {a[-1]:.2f} G seuil: {thresholds['accel']:.1f} G"
|
|
||||||
+ (f" ── pic dernier tir: {pa:.2f} G ({pa/thresholds['accel']*100:.0f}%)" if pa else "")
|
|
||||||
+ f" [NUM7=+0.1 NUM1=-0.1]")
|
|
||||||
titles[1].set_text(
|
|
||||||
f"Gyroscope val: {g[-1]:.0f} dps seuil: {thresholds['gyro']:.0f} dps"
|
|
||||||
+ (f" ── pic dernier tir: {pg:.0f} dps ({pg/thresholds['gyro']*100:.0f}%)" if pg else "")
|
|
||||||
+ f" [NUM8=+10 NUM2=-10]")
|
|
||||||
titles[2].set_text(
|
|
||||||
f"Microphone PDM val: {m[-1]:.0f} seuil: {thresholds['audio']}"
|
|
||||||
+ (f" ── pic dernier tir: {pm:.0f} ({pm/thresholds['audio']*100:.0f}%)" if pm else "")
|
|
||||||
+ 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(
|
||||||
@ -355,7 +330,6 @@ def update(frame):
|
|||||||
|
|
||||||
return (line_a, line_g, line_m, line_s,
|
return (line_a, line_g, line_m, line_s,
|
||||||
thr_a, thr_g, thr_m,
|
thr_a, thr_g, thr_m,
|
||||||
peak_a, peak_g, peak_m,
|
|
||||||
*titles, status_txt, debug_txt)
|
*titles, status_txt, debug_txt)
|
||||||
|
|
||||||
def on_key(event):
|
def on_key(event):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user