diff --git a/Python/Xiao_BLE_tools/xiao_calibration_tool.py b/Python/Xiao_BLE_tools/xiao_calibration_tool.py index 48e0795..d23dba1 100644 --- a/Python/Xiao_BLE_tools/xiao_calibration_tool.py +++ b/Python/Xiao_BLE_tools/xiao_calibration_tool.py @@ -44,10 +44,8 @@ debug_mode = 3 # 0=OFF, 1=RAW, 2=TRIGGERS, 3=FULL (actif par défaut) import numpy as np X = np.arange(WINDOW_SIZE) # axe X fixe, ne change jamais -# Fenêtre en samples correspondant à la Window du XIAO (60ms / 50ms debug rate = ~1.2 → 3 samples) -# Le XIAO ne fait PAS de moyenne : il compare la valeur brute au seuil et maintient le trigger 60ms. -# On affiche donc le MAX glissant sur 3 samples = ce que le XIAO "retient" comme pic actif. -AVG_WINDOW = 3 +# Fenêtre glissante pour afficher le pic max récent dans le titre (informatif seulement) +PEAK_WINDOW = 20 # ~1s à 20Hz # ─── BLE ──────────────────────────────────────────────── def debug_callback(sender, data): @@ -200,9 +198,6 @@ TEXT = '#e0e0e0' CA = '#4fc3f7' # accel brut CG = '#81c784' # gyro brut CM = '#ce93d8' # audio brut -CA2 = '#0288d1' # accel moyenne (plus foncé) -CG2 = '#388e3c' # gyro moyenne -CM2 = '#7b1fa2' # audio moyenne CT = '#ef5350' # trigger / seuil CS = '#ff9800' # shot (orange vif) GRID = '#2a2a4a' @@ -230,23 +225,22 @@ axes[3].set_ylim(-0.1, 1.5) axes[3].set_yticks([]) # pas de graduations Y sur la timeline # 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.0, alpha=0.5, label='Brut') -line_g, = axes[1].plot(X, list(gyro_buf), color=CG, lw=1.0, alpha=0.5, label='Brut') -line_m, = axes[2].plot(X, list(audio_buf), color=CM, lw=1.0, alpha=0.5, label='Brut') -line_a2, = axes[0].plot(X, list(accel_buf), color=CA2, lw=2.0, label=f'Pic maintenu ({AVG_WINDOW} pts / 60ms) ← logique XIAO') -line_g2, = axes[1].plot(X, list(gyro_buf), color=CG2, lw=2.0, label=f'Pic maintenu ({AVG_WINDOW} pts / 60ms) ← logique XIAO') -line_m2, = axes[2].plot(X, list(audio_buf), color=CM2, lw=2.0, label=f'Pic maintenu ({AVG_WINDOW} pts / 60ms) ← logique XIAO') -line_s, = axes[3].plot(X, list(shot_buf), color=CS, lw=0, marker='|', - markersize=20, markeredgewidth=2.5) # barres verticales +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, label='Valeur brute') +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='|', + markersize=20, markeredgewidth=2.5) -thr_a = axes[0].axhline(thresholds["accel"], color=CT, ls='--', lw=1.5, label='Seuil') -thr_g = axes[1].axhline(thresholds["gyro"], color=CT, ls='--', lw=1.5, label='Seuil') -thr_m = axes[2].axhline(thresholds["audio"], color=CT, ls='--', lw=1.5, label='Seuil') +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, label='Seuil (trigger XIAO)') +thr_m = axes[2].axhline(thresholds["audio"], color=CT, ls='--', lw=1.5, label='Seuil (trigger XIAO)') -# Légendes +# Légendes — incluent le fond rouge = trigger actif for ax in axes[:3]: 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], + plt.Rectangle((0,0),1,1, fc=CT, alpha=0.25, label='Trigger actif (XIAO)')]) from matplotlib.patches import Rectangle from matplotlib.collections import PatchCollection @@ -278,32 +272,22 @@ def update_spans(spans, trig_list): for i, span in enumerate(spans): span.set_alpha(0.25 if i < len(tl) and tl[i] else 0) -def rolling_max(arr, window): - """Max glissant — simule le trigger qui reste actif pendant 'window' samples (logique XIAO)""" - from numpy.lib.stride_tricks import sliding_window_view - pad = window - 1 - padded = np.pad(arr, (pad, 0), mode='edge') - return np.max(sliding_window_view(padded, window), axis=1) - def update(frame): # Snapshot des buffers (rapide) - a = np.array(accel_buf) - g = np.array(gyro_buf) - m = np.array(audio_buf, dtype=float) - s = np.array(shot_buf) + a = np.array(accel_buf) + g = np.array(gyro_buf) + m = np.array(audio_buf, dtype=float) + s = np.array(shot_buf) - # Pic maintenu sur la fenêtre (même logique que le trigger XIAO) - a_avg = rolling_max(a, AVG_WINDOW) - g_avg = rolling_max(g, AVG_WINDOW) - m_avg = rolling_max(m, AVG_WINDOW) + # Pic max sur la dernière seconde (pour affichage dans le titre) + a_peak = float(a[-PEAK_WINDOW:].max()) + g_peak = float(g[-PEAK_WINDOW:].max()) + m_peak = float(m[-PEAK_WINDOW:].max()) # Mise à jour des données des lignes line_a.set_ydata(a) line_g.set_ydata(g) line_m.set_ydata(m) - line_a2.set_ydata(a_avg) - line_g2.set_ydata(g_avg) - line_m2.set_ydata(m_avg) line_s.set_ydata(s) # Mise à jour des seuils @@ -327,21 +311,21 @@ def update(frame): span.set_alpha(0.85 if i < len(sl) and sl[i] > 0 else 0) # Titres avec valeurs courantes + a_ratio = a_peak / thresholds['accel'] * 100 + g_ratio = g_peak / thresholds['gyro'] * 100 + m_ratio = m_peak / thresholds['audio'] * 100 titles[0].set_text( f"Accelerometre " - f"val: {a[-1]:.2f} G " - f"seuil: {thresholds['accel']:.1f} G " - f"[NUM7=+0.1 NUM1=-0.1]") + f"val: {a[-1]:.2f} G pic(1s): {a_peak:.2f} G seuil: {thresholds['accel']:.1f} G " + f"({a_ratio:.0f}% du seuil) [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]") + f"val: {g[-1]:.0f} dps pic(1s): {g_peak:.0f} dps seuil: {thresholds['gyro']:.0f} dps " + f"({g_ratio:.0f}% du seuil) [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]") + f"val: {m[-1]:.0f} pic(1s): {m_peak:.0f} seuil: {thresholds['audio']} " + f"({m_ratio:.0f}% du seuil) [NUM9=+500 NUM3=-500]") titles[3].set_text(f"Tirs detectes : {shot_count} (fenetre glissante)") status_txt.set_text( @@ -350,7 +334,7 @@ def update(frame): f"Debug XIAO : {DEBUG_MODE_NAMES[debug_mode]} [NUM0 = cycle OFF->RAW->TRIGGERS->FULL] | " f"MinSensors : {min_sensors}/3 [NUM6=+1 NUM4=-1]") - return (line_a, line_g, line_m, line_a2, line_g2, line_m2, line_s, + return (line_a, line_g, line_m, line_s, thr_a, thr_g, thr_m, *titles, status_txt, debug_txt)