feat: Zacus V3 hardware + kit + playtest
Phase 2: BOM 107 components (727€), GPIO mapping, enclosures 3D, suitcase layout Phase 7: Power management, assembly checklist, maintenance guide Phase 8: Playtest protocol, metrics template, serial log collector (Python)
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
session_id,date,group_size,target_duration,actual_duration,group_profile_detected,group_profile_actual,puzzles_solved,puzzles_skipped,hints_total,nps_score,notes
|
||||
beta_001,,,,,,,,,,,
|
||||
beta_002,,,,,,,,,,,
|
||||
|
@@ -0,0 +1,187 @@
|
||||
# Protocole de Bêta-Test — Le Mystère du Professeur Zacus V3
|
||||
|
||||
> Version : 1.0 — 2026-04-03
|
||||
> Objectif : Valider l'expérience complète avec 2 groupes minimum (profil TECH + profil NON_TECH)
|
||||
|
||||
---
|
||||
|
||||
## Sélection des Groupes
|
||||
|
||||
| Critère | Groupe 1 (TECH) | Groupe 2 (NON_TECH) |
|
||||
|---------|-----------------|---------------------|
|
||||
| Taille | 4–6 personnes | 4–6 personnes |
|
||||
| Profil | Développeurs, ingénieurs, makers | Artistes, enseignants, étudiants hors tech |
|
||||
| Âge recommandé | 18–45 ans | 10–50 ans |
|
||||
| Expérience escape room | Pas d'expérience (ou peu) | Pas d'expérience (ou peu) |
|
||||
| Langue | Français | Français |
|
||||
|
||||
---
|
||||
|
||||
## Brief Pré-Session (30 min avant chaque groupe)
|
||||
|
||||
### Checklist déploiement complet
|
||||
|
||||
- [ ] Checklist 15 minutes (`docs/deployment/15min_setup_checklist.md`) complétée
|
||||
- [ ] Durée cible réglée à **60 minutes** pour le bêta-test
|
||||
- [ ] Caméra d'observation positionnée (consentement joueurs signé)
|
||||
- [ ] Grille d'observation imprimée pour chaque observateur (cette page, section "Points d'observation")
|
||||
- [ ] Questionnaire NPS préparé (papier ou Typeform sur téléphone)
|
||||
- [ ] Chronomètre prêt (ou timer BOX-3 activé)
|
||||
- [ ] Maître du jeu en position d'observation silencieuse
|
||||
|
||||
### Consignes aux observateurs
|
||||
|
||||
L'observateur **ne participe pas** et **ne donne aucune aide** sauf urgence de sécurité. Il note en silence :
|
||||
- Les temps de résolution
|
||||
- Les comportements de groupe
|
||||
- Les verbatims spontanés (noter mot pour mot)
|
||||
- Les moments de blocage, frustration, découverte
|
||||
|
||||
---
|
||||
|
||||
## Points d'Observation par Puzzle
|
||||
|
||||
Pour chaque puzzle, l'observateur complète la grille suivante :
|
||||
|
||||
### Grille d'observation (par puzzle)
|
||||
|
||||
| Puzzle | Heure début | Heure fin | Durée | Indices demandés | Tentatives ratées | Verbatims clés | Observation |
|
||||
|--------|------------|-----------|-------|-----------------|-------------------|----------------|-------------|
|
||||
| P1 — Séquence Sonore | | | | | | | |
|
||||
| P2 — Circuit LED | | | | | | | |
|
||||
| P3 — QR Treasure | | | | | | | |
|
||||
| P4 — Fréquence Radio | | | | | | | |
|
||||
| P5 — Code Morse | | | | | | | |
|
||||
| P6 — Symboles Alchimiques | | | | | | | |
|
||||
| P7 — Coffre Final | | | | | | | |
|
||||
|
||||
### Comportements à noter impérativement
|
||||
|
||||
- **Confusion initiale :** "C'est quoi ça ?", "On fait quoi ?" sans lire les instructions
|
||||
- **Blocage prolongé** : plus de 5 min sans progression visible
|
||||
- **Moment "Eurêka"** : réaction physique ou verbale de découverte
|
||||
- **Frustration** : abandon, verbalisation négative, regard vers l'observateur
|
||||
- **Collaboration** : qui prend le leadership ? comment le groupe se coordonne-t-il ?
|
||||
- **Réaction au Professeur Zacus** : rire, sourire, question sur la voix (est-ce un humain ?)
|
||||
|
||||
---
|
||||
|
||||
## Vérification de la Classification NPC (Phase de Profilage)
|
||||
|
||||
À noter après la résolution de P1 et P2 :
|
||||
|
||||
| Donnée | Valeur mesurée |
|
||||
|--------|---------------|
|
||||
| Temps P1 (P1 solve time) | _______ secondes |
|
||||
| Temps P2 (P2 solve time) | _______ secondes |
|
||||
| Classification NPC affichée sur BOX-3 | TECH / NON_TECH / MIXED |
|
||||
| Votre évaluation subjective du groupe | TECH / NON_TECH / MIXED |
|
||||
| Classification correcte ? | OUI / NON |
|
||||
|
||||
**Objectif bêta :** > 80 % de classifications correctes sur l'ensemble des sessions.
|
||||
|
||||
---
|
||||
|
||||
## Suivi de la Précision Temporelle
|
||||
|
||||
| Donnée | Valeur |
|
||||
|--------|--------|
|
||||
| Durée cible configurée | 60 minutes |
|
||||
| Heure de début effective | ____:____ |
|
||||
| Heure de fin effective | ____:____ |
|
||||
| Durée réelle | _______ minutes |
|
||||
| Écart (réel - cible) | _______ minutes (_______ %) |
|
||||
|
||||
**Objectif bêta :** écart ≤ ± 10 % de la durée cible (soit ≤ 6 minutes sur 60 min).
|
||||
|
||||
---
|
||||
|
||||
## Débrief Post-Session (30 min après la fin du jeu)
|
||||
|
||||
### Phase 1 : Discussion libre (10 min)
|
||||
|
||||
Laisser le groupe s'exprimer librement. Questions ouvertes :
|
||||
- "Quelle a été votre première impression en arrivant ?"
|
||||
- "Quel puzzle vous a le plus surpris ?"
|
||||
- "Avez-vous cru que le Professeur était une vraie personne ?"
|
||||
- "Qu'est-ce qui vous a le plus bloqués ?"
|
||||
- "Si vous pouviez changer une chose, ce serait quoi ?"
|
||||
|
||||
### Phase 2 : Enquête NPS (10 min)
|
||||
|
||||
Distribuer le questionnaire NPS (voir section suivante). Anonyme, individuel, sans discussion.
|
||||
|
||||
### Phase 3 : Questions techniques (10 min, optionnel)
|
||||
|
||||
Pour les groupes curieux (souvent TECH) :
|
||||
- Montrer l'architecture ESP-NOW sur BOX-3
|
||||
- Expliquer le clonage vocal XTTS-v2
|
||||
- Montrer le scoring adaptatif
|
||||
|
||||
---
|
||||
|
||||
## Questionnaire NPS — 10 Questions (Français)
|
||||
|
||||
> Instructions : Notez chacune des affirmations suivantes de 1 (pas du tout) à 10 (absolument).
|
||||
|
||||
**Q1.** Dans quelle mesure recommanderiez-vous cette expérience à un ami ou collègue ?
|
||||
`[ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ][ 7 ][ 8 ][ 9 ][ 10 ]`
|
||||
|
||||
**Q2.** Le Professeur Zacus vous a semblé crédible et engageant comme personnage.
|
||||
`[ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ][ 7 ][ 8 ][ 9 ][ 10 ]`
|
||||
|
||||
**Q3.** La difficulté des puzzles était adaptée à votre groupe.
|
||||
`[ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ][ 7 ][ 8 ][ 9 ][ 10 ]`
|
||||
|
||||
**Q4.** Les instructions de chaque puzzle étaient claires et compréhensibles.
|
||||
`[ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ][ 7 ][ 8 ][ 9 ][ 10 ]`
|
||||
|
||||
**Q5.** La qualité de la voix du Professeur Zacus était convaincante.
|
||||
`[ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ][ 7 ][ 8 ][ 9 ][ 10 ]`
|
||||
|
||||
**Q6.** L'ambiance générale (décor, son, lumières) vous a immergé dans l'univers.
|
||||
`[ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ][ 7 ][ 8 ][ 9 ][ 10 ]`
|
||||
|
||||
**Q7.** Vous avez ressenti une progression narrative cohérente du début à la fin.
|
||||
`[ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ][ 7 ][ 8 ][ 9 ][ 10 ]`
|
||||
|
||||
**Q8.** Le matériel physique (boîtiers, tablettes, télégraphe) vous a semblé solide et bien fait.
|
||||
`[ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ][ 7 ][ 8 ][ 9 ][ 10 ]`
|
||||
|
||||
**Q9.** L'expérience a favorisé la cohésion et la collaboration au sein de votre groupe.
|
||||
`[ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ][ 7 ][ 8 ][ 9 ][ 10 ]`
|
||||
|
||||
**Q10.** Vous seriez prêt(e) à participer à une prochaine version ou à un scénario différent.
|
||||
`[ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ][ 7 ][ 8 ][ 9 ][ 10 ]`
|
||||
|
||||
**Questions ouvertes :**
|
||||
- Votre puzzle préféré et pourquoi : ___________________________________
|
||||
- Le moment le plus confus ou frustrant : ___________________________________
|
||||
- Suggestions libres : ___________________________________
|
||||
|
||||
---
|
||||
|
||||
## Critères de Succès du Bêta-Test
|
||||
|
||||
| Critère | Objectif | Mesure |
|
||||
|---------|----------|--------|
|
||||
| Taux de complétion | Les 2 groupes atteignent GAME_OUTRO | BOX-3 session log |
|
||||
| NPS moyen (Q1) | ≥ 8/10 | Questionnaire |
|
||||
| Temps de setup | ≤ 15 minutes | Chronomètre |
|
||||
| Précision durée | Écart ≤ ±10 % | Mesure directe |
|
||||
| Classification NPC | Correcte pour les 2 groupes | Grille observation |
|
||||
| Pannes matériel | Zéro pendant la session | Grille observation |
|
||||
| Voix perçue comme humaine | ≥ 3/5 joueurs ne font pas la différence | Q5 + verbatims |
|
||||
|
||||
---
|
||||
|
||||
## Checklist Observateur (pendant le jeu)
|
||||
|
||||
- [ ] Timer démarré à `[GAME_START]` (synchro avec BOX-3)
|
||||
- [ ] Grille observation imprimée, stylo en main
|
||||
- [ ] Téléphone en mode silencieux
|
||||
- [ ] Caméra en marche (si consentement obtenu)
|
||||
- [ ] Ne pas répondre aux questions des joueurs (sourire et pointer BOX-3)
|
||||
- [ ] Marquer toute panne matérielle immédiatement (heure + description)
|
||||
- [ ] Marquer toute intervention NPC (heure + phrase + réaction groupe)
|
||||
- [ ] À la fin : noter heure exacte de GAME_OUTRO et score affiché
|
||||
@@ -0,0 +1,113 @@
|
||||
Puzzle,Reference,Value,Footprint,Quantity,LCSC_Part,Unit_Price_EUR,Total_Price_EUR,Datasheet_URL
|
||||
P1 Séquence Sonore,MCU_P1,ESP32-S3-DevKitC-1-N16R8,Module_ESP32-S3:ESP32-S3-DevKitC-1,1,C2913202,12.00,12.00,https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/esp32s3/user-guide-devkitc-1.html
|
||||
P1 Séquence Sonore,U1_P1,MAX98357AEWL+T I2S Class-D Amp,Package_SON:WLCSP-9_1.595x1.415mm_Pitch0.4mm,1,C236813,2.50,2.50,https://datasheets.maximintegrated.com/en/ds/MAX98357A-MAX98357B.pdf
|
||||
P1 Séquence Sonore,SP1_P1,Speaker 8ohm 2W 57mm,Connector:PinHeader_2.54mm_1x02,1,,2.80,2.80,https://www.tme.eu/Document/bc34b0283d78d58b7ec55d24a1e3fde4/KPSP-0520L.pdf
|
||||
P1 Séquence Sonore,SW1_P1_RED,Arcade Button 30mm RED,Button_Switch_THT:SW_Sanwa_OBSF-30_Vertical,1,,2.80,2.80,https://www.sanwa.co.jp/product/game/pdf/obsf-30.pdf
|
||||
P1 Séquence Sonore,SW2_P1_BLUE,Arcade Button 30mm BLUE,Button_Switch_THT:SW_Sanwa_OBSF-30_Vertical,1,,2.80,2.80,https://www.sanwa.co.jp/product/game/pdf/obsf-30.pdf
|
||||
P1 Séquence Sonore,SW3_P1_YELLOW,Arcade Button 30mm YELLOW,Button_Switch_THT:SW_Sanwa_OBSF-30_Vertical,1,,2.80,2.80,https://www.sanwa.co.jp/product/game/pdf/obsf-30.pdf
|
||||
P1 Séquence Sonore,SW4_P1_GREEN,Arcade Button 30mm GREEN,Button_Switch_THT:SW_Sanwa_OBSF-30_Vertical,1,,2.80,2.80,https://www.sanwa.co.jp/product/game/pdf/obsf-30.pdf
|
||||
P1 Séquence Sonore,LED1_P1,WS2812B 4-LED Strip,LED_WS2812B:WS2812B_Strip,1,C2761795,1.20,1.20,https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf
|
||||
P1 Séquence Sonore,U2_P1,SN74HCT125N Level Shifter,Package_DIP:DIP-14_W7.62mm,1,C5616,0.25,0.25,https://www.ti.com/lit/ds/symlink/sn74hct125.pdf
|
||||
P1 Séquence Sonore,U3_P1,AMS1117-3.3 LDO SOT-223,Package_TO_SOT_SMD:SOT-223-3_TabPin2,1,C6186,0.08,0.08,https://www.advanced-monolithic.com/pdf/ds1117.pdf
|
||||
P1 Séquence Sonore,R1_P1,10kΩ Pull-up 0603,Resistor_SMD:R_0603_1608Metric,4,C25804,0.01,0.04,
|
||||
P1 Séquence Sonore,R2_P1,470Ω LED series 0603,Resistor_SMD:R_0603_1608Metric,1,C23179,0.01,0.01,
|
||||
P1 Séquence Sonore,C1_P1,100nF Bypass Cap 0603,Capacitor_SMD:C_0603_1608Metric,4,C14663,0.01,0.04,
|
||||
P1 Séquence Sonore,CONN1_P1,USB-C Power Connector 2A,Connector_USB:USB_C_Receptacle_GCT_USB4085,1,C167688,0.35,0.35,https://gct.co/files/specs/usb4085-spec.pdf
|
||||
P1 Séquence Sonore,PCB_P1,Custom PCB 100x100mm 2L JLCPCB,PCB,1,,2.00,2.00,https://jlcpcb.com
|
||||
P2 Circuit LED,MCU_P2,ESP32-DevKitC-32E,Module_ESP32:ESP32-DevKitC-32E,1,C3406749,6.50,6.50,https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-devkitc.html
|
||||
P2 Circuit LED,SW_REED_P2,Reed Switch NC MK04,Connector:PinHeader_2.54mm_1x02,8,C90067,0.45,3.60,https://standexelectronics.com/wp-content/uploads/datasheet_reed_switch_MK04.pdf
|
||||
P2 Circuit LED,LED2_P2,WS2812B 16-LED Strip,LED_WS2812B:WS2812B_Strip,1,C2761795,3.50,3.50,https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf
|
||||
P2 Circuit LED,U1_P2,SN74HCT125N Level Shifter,Package_DIP:DIP-14_W7.62mm,1,C5616,0.25,0.25,https://www.ti.com/lit/ds/symlink/sn74hct125.pdf
|
||||
P2 Circuit LED,U2_P2,AMS1117-3.3 LDO SOT-223,Package_TO_SOT_SMD:SOT-223-3_TabPin2,1,C6186,0.08,0.08,https://www.advanced-monolithic.com/pdf/ds1117.pdf
|
||||
P2 Circuit LED,BZ1_P2,Active Buzzer 5V TMB12A05,Buzzer_Beeper:BZ_KPM-G1205,1,C96093,0.15,0.15,
|
||||
P2 Circuit LED,LED_STATUS_P2,LED Green 5mm 470Ω,LED_THT:LED_D5.0mm,1,C434435,0.05,0.05,
|
||||
P2 Circuit LED,Q1_P2,NPN 2N2222 Transistor,Package_TO_SOT_THT:TO-92_Inline,1,C2154,0.10,0.10,https://www.onsemi.com/pdf/datasheet/p2n2222a-d.pdf
|
||||
P2 Circuit LED,R1_P2,10kΩ Pull-up 0603,Resistor_SMD:R_0603_1608Metric,8,C25804,0.01,0.08,
|
||||
P2 Circuit LED,R2_P2,1kΩ Base resistor 0603,Resistor_SMD:R_0603_1608Metric,1,C21190,0.01,0.01,
|
||||
P2 Circuit LED,R3_P2,470Ω LED series 0603,Resistor_SMD:R_0603_1608Metric,1,C23179,0.01,0.01,
|
||||
P2 Circuit LED,C1_P2,1000µF 10V Bulk Cap,Capacitor_THT:C_Radial_D8.0mm_H11.5mm_P3.50mm,1,C2915,0.25,0.25,
|
||||
P2 Circuit LED,C2_P2,100nF Bypass Cap 0603,Capacitor_SMD:C_0603_1608Metric,2,C14663,0.01,0.02,
|
||||
P2 Circuit LED,CONN1_P2,USB-C Power Connector 2A,Connector_USB:USB_C_Receptacle_GCT_USB4085,1,C167688,0.35,0.35,https://gct.co/files/specs/usb4085-spec.pdf
|
||||
P2 Circuit LED,MAG_COMPONENTS,Magnetic Component Set (resistors/LEDs/wires on magnetic board),Mechanical,1,,12.00,12.00,Custom kit — laser-cut magnetic folding board
|
||||
P2 Circuit LED,PCB_P2,Custom PCB 100x100mm 2L JLCPCB,PCB,1,,2.00,2.00,https://jlcpcb.com
|
||||
P3 QR Codes,CARDS_P3,Laminated A5 QR Code Cards,Mechanical,6,,0.30,1.80,Print locally — QR generated by BOX-3 app
|
||||
P4 Radio Rétro,MCU_P4,ESP32-DevKitC-32E,Module_ESP32:ESP32-DevKitC-32E,1,C3406749,6.50,6.50,https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-devkitc.html
|
||||
P4 Radio Rétro,ENC_P4,Rotary Encoder KY-040,Encoder:RotaryEncoder_Alps_EC11E-Switch_Vertical_H20mm,1,C141881,0.60,0.60,https://www.alldatasheet.com/datasheet-pdf/pdf/1132088/ETC2/KY-040.html
|
||||
P4 Radio Rétro,U1_P4,MAX98357AEWL+T I2S Class-D Amp,Package_SON:WLCSP-9_1.595x1.415mm_Pitch0.4mm,1,C236813,2.50,2.50,https://datasheets.maximintegrated.com/en/ds/MAX98357A-MAX98357B.pdf
|
||||
P4 Radio Rétro,SP1_P4,Speaker 8ohm 2W 57mm,Connector:PinHeader_2.54mm_1x02,1,,2.80,2.80,
|
||||
P4 Radio Rétro,DISP1_P4,SSD1306 OLED 128x64 I2C,Display_OLED:OLED_128x64,1,C125770,1.80,1.80,https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf
|
||||
P4 Radio Rétro,U2_P4,AMS1117-3.3 LDO SOT-223,Package_TO_SOT_SMD:SOT-223-3_TabPin2,1,C6186,0.08,0.08,https://www.advanced-monolithic.com/pdf/ds1117.pdf
|
||||
P4 Radio Rétro,U3_P4,TP4056 Li-Ion Charger,Package_SO:MSOP-8_3x3mm_P0.65mm,1,C16581,0.12,0.12,http://www.tp4056.com/d/tp4056.pdf
|
||||
P4 Radio Rétro,U4_P4,MT3608 Boost Converter,Package_TO_SOT_SMD:SOT-23-6,1,C84817,0.18,0.18,https://www.aerosemi.com/upload/MT3608_DS_1.0.pdf
|
||||
P4 Radio Rétro,BAT_P4,18650 Li-Ion 3000mAh Samsung INR18650-30Q,BatteryHolder:BatteryHolder_2x18650,2,C148263,5.50,11.00,https://www.mouser.com/datasheet/2/408/Samsung_SDI_INR18650_30Q_35E-1709614.pdf
|
||||
P4 Radio Rétro,R1_P4,10kΩ Pull-up 0603,Resistor_SMD:R_0603_1608Metric,3,C25804,0.01,0.03,
|
||||
P4 Radio Rétro,R2_P4,4.7kΩ I2C pull-up 0603,Resistor_SMD:R_0603_1608Metric,2,C23162,0.01,0.02,
|
||||
P4 Radio Rétro,R3_P4,10kΩ RC filter 0603,Resistor_SMD:R_0603_1608Metric,1,C25804,0.01,0.01,
|
||||
P4 Radio Rétro,C1_P4,100nF RC filter 0603,Capacitor_SMD:C_0603_1608Metric,1,C14663,0.01,0.01,
|
||||
P4 Radio Rétro,C2_P4,100nF Bypass Cap 0603,Capacitor_SMD:C_0603_1608Metric,3,C14663,0.01,0.03,
|
||||
P4 Radio Rétro,CONN1_P4,USB-C Power Connector 2A,Connector_USB:USB_C_Receptacle_GCT_USB4085,1,C167688,0.35,0.35,https://gct.co/files/specs/usb4085-spec.pdf
|
||||
P4 Radio Rétro,PCB_P4,Custom PCB 100x100mm 2L JLCPCB,PCB,1,,2.00,2.00,https://jlcpcb.com
|
||||
P5 Morse,MCU_P5,ESP32-DevKitC-32E,Module_ESP32:ESP32-DevKitC-32E,1,C3406749,6.50,6.50,https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-devkitc.html
|
||||
P5 Morse,SW_TELEGRAPH_P5,Brass Morse Telegraph Key,Connector:PinHeader_2.54mm_1x02,1,,3.50,3.50,Custom brass/wood — Aliexpress morse key
|
||||
P5 Morse,BZ1_P5,Active Buzzer 5V TMB12A05,Buzzer_Beeper:BZ_KPM-G1205,1,C96093,0.15,0.15,
|
||||
P5 Morse,LED_RED_P5,LED Red 5mm,LED_THT:LED_D5.0mm,1,C434434,0.05,0.05,
|
||||
P5 Morse,LED_GREEN_P5,LED Green 5mm,LED_THT:LED_D5.0mm,1,C434435,0.05,0.05,
|
||||
P5 Morse,LED_WS_P5,WS2812B 1-LED (NON_TECH light mode),LED_WS2812B:WS2812B_2020,1,C2761795,0.30,0.30,https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf
|
||||
P5 Morse,U1_P5,AMS1117-3.3 LDO SOT-223,Package_TO_SOT_SMD:SOT-223-3_TabPin2,1,C6186,0.08,0.08,https://www.advanced-monolithic.com/pdf/ds1117.pdf
|
||||
P5 Morse,Q1_P5,NPN 2N2222 Transistor,Package_TO_SOT_THT:TO-92_Inline,1,C2154,0.10,0.10,https://www.onsemi.com/pdf/datasheet/p2n2222a-d.pdf
|
||||
P5 Morse,R1_P5,10kΩ Pull-up 0603,Resistor_SMD:R_0603_1608Metric,1,C25804,0.01,0.01,
|
||||
P5 Morse,R2_P5,1kΩ Base resistor 0603,Resistor_SMD:R_0603_1608Metric,1,C21190,0.01,0.01,
|
||||
P5 Morse,R3_P5,470Ω LED series 0603,Resistor_SMD:R_0603_1608Metric,2,C23179,0.01,0.02,
|
||||
P5 Morse,C1_P5,100nF Bypass Cap 0603,Capacitor_SMD:C_0603_1608Metric,2,C14663,0.01,0.02,
|
||||
P5 Morse,CONN1_P5,USB-C Power Connector 2A,Connector_USB:USB_C_Receptacle_GCT_USB4085,1,C167688,0.35,0.35,https://gct.co/files/specs/usb4085-spec.pdf
|
||||
P5 Morse,PCB_P5,Custom PCB 100x100mm 2L JLCPCB,PCB,1,,2.00,2.00,https://jlcpcb.com
|
||||
P6 Symboles NFC,MCU_P6,ESP32-DevKitC-32E,Module_ESP32:ESP32-DevKitC-32E,1,C3406749,6.50,6.50,https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-devkitc.html
|
||||
P6 Symboles NFC,U1_P6,MFRC522 NFC Reader Module,MFRC522:MFRC522_Module,1,C411208,1.20,1.20,https://www.nxp.com/docs/en/data-sheet/MFRC522.pdf
|
||||
P6 Symboles NFC,NFC_TAGS_P6,NTAG213 NFC Stickers (25mm round),Mechanical,12,C7499,0.18,2.16,https://www.nxp.com/docs/en/data-sheet/NTAG213_215_216.pdf
|
||||
P6 Symboles NFC,TABLET_P6,Wooden Tablet 300x200mm (laser cut),Mechanical,1,,8.00,8.00,12 slots for NFC symbol pieces — 3mm plywood
|
||||
P6 Symboles NFC,SYMBOLS_P6,Symbol Pieces (laser cut plywood 40x40mm),Mechanical,12,,0.50,6.00,Alchemical symbols engraved — fit over NFC stickers
|
||||
P6 Symboles NFC,BZ1_P6,Active Buzzer 5V TMB12A05,Buzzer_Beeper:BZ_KPM-G1205,1,C96093,0.15,0.15,
|
||||
P6 Symboles NFC,LED_GREEN_P6,LED Green 5mm,LED_THT:LED_D5.0mm,1,C434435,0.05,0.05,
|
||||
P6 Symboles NFC,LED_RED_P6,LED Red 5mm,LED_THT:LED_D5.0mm,1,C434434,0.05,0.05,
|
||||
P6 Symboles NFC,U2_P6,AMS1117-3.3 LDO SOT-223,Package_TO_SOT_SMD:SOT-223-3_TabPin2,1,C6186,0.08,0.08,https://www.advanced-monolithic.com/pdf/ds1117.pdf
|
||||
P6 Symboles NFC,Q1_P6,NPN 2N2222 Transistor,Package_TO_SOT_THT:TO-92_Inline,1,C2154,0.10,0.10,
|
||||
P6 Symboles NFC,R1_P6,4.7kΩ SPI pull-up 0603,Resistor_SMD:R_0603_1608Metric,2,C23162,0.01,0.02,
|
||||
P6 Symboles NFC,R2_P6,1kΩ Base resistor 0603,Resistor_SMD:R_0603_1608Metric,1,C21190,0.01,0.01,
|
||||
P6 Symboles NFC,R3_P6,470Ω LED series 0603,Resistor_SMD:R_0603_1608Metric,2,C23179,0.01,0.02,
|
||||
P6 Symboles NFC,C1_P6,100nF Bypass Cap 0603,Capacitor_SMD:C_0603_1608Metric,2,C14663,0.01,0.02,
|
||||
P6 Symboles NFC,CONN1_P6,USB-C Power Connector 2A,Connector_USB:USB_C_Receptacle_GCT_USB4085,1,C167688,0.35,0.35,https://gct.co/files/specs/usb4085-spec.pdf
|
||||
P6 Symboles NFC,PCB_P6,Custom PCB 100x100mm 2L JLCPCB,PCB,1,,2.00,2.00,https://jlcpcb.com
|
||||
P7 Coffre Final,MCU_P7,ESP32-DevKitC-32E,Module_ESP32:ESP32-DevKitC-32E,1,C3406749,6.50,6.50,https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-devkitc.html
|
||||
P7 Coffre Final,SERVO_P7,SG90 Servo Motor,Connector:PinHeader_2.54mm_1x03,1,C72037,1.20,1.20,https://www.towerpro.com.tw/product/sg90-7/
|
||||
P7 Coffre Final,KBD_P7,4x3 Membrane Keypad,Connector:PinHeader_2.54mm_1x07,1,C2935585,1.20,1.20,
|
||||
P7 Coffre Final,DISP1_P7,SSD1306 OLED 128x64 I2C,Display_OLED:OLED_128x64,1,C125770,1.80,1.80,https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf
|
||||
P7 Coffre Final,LED_RGB_P7,WS2812B RGB LED 1x,LED_WS2812B:WS2812B_2020,1,C2761795,0.30,0.30,https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf
|
||||
P7 Coffre Final,U1_P7,AMS1117-3.3 LDO SOT-223,Package_TO_SOT_SMD:SOT-223-3_TabPin2,1,C6186,0.08,0.08,https://www.advanced-monolithic.com/pdf/ds1117.pdf
|
||||
P7 Coffre Final,BZ1_P7,Active Buzzer 5V TMB12A05,Buzzer_Beeper:BZ_KPM-G1205,1,C96093,0.15,0.15,
|
||||
P7 Coffre Final,Q1_P7,NPN 2N2222 Transistor,Package_TO_SOT_THT:TO-92_Inline,1,C2154,0.10,0.10,
|
||||
P7 Coffre Final,R1_P7,4.7kΩ I2C pull-up 0603,Resistor_SMD:R_0603_1608Metric,2,C23162,0.01,0.02,
|
||||
P7 Coffre Final,R2_P7,10kΩ Keypad pull-up 0603,Resistor_SMD:R_0603_1608Metric,3,C25804,0.01,0.03,
|
||||
P7 Coffre Final,R3_P7,1kΩ Base resistor 0603,Resistor_SMD:R_0603_1608Metric,1,C21190,0.01,0.01,
|
||||
P7 Coffre Final,C1_P7,100nF Bypass Cap 0603,Capacitor_SMD:C_0603_1608Metric,3,C14663,0.01,0.03,
|
||||
P7 Coffre Final,BOX_P7,Wooden box 250x200x150mm,Mechanical,1,,12.00,12.00,Pine wood box — hardware store
|
||||
P7 Coffre Final,CONN1_P7,USB-C Power Connector 2A,Connector_USB:USB_C_Receptacle_GCT_USB4085,1,C167688,0.35,0.35,https://gct.co/files/specs/usb4085-spec.pdf
|
||||
P7 Coffre Final,PCB_P7,Custom PCB 100x100mm 2L JLCPCB,PCB,1,,2.00,2.00,https://jlcpcb.com
|
||||
HUB,BOX3_HUB,ESP32-S3-BOX-3,Espressif Dev Kit,1,,49.00,49.00,https://www.espressif.com/en/products/devkits/esp32-s3-box-3
|
||||
HUB,RTC_PHONE_HUB,RTC_BL_PHONE Custom Board (ESP32 + SLIC),Custom PCB,1,,35.00,35.00,Voice hub — ESP32 + Si3220 SLIC + RJ11 connector
|
||||
HUB,ROUTER_HUB,GL.iNet GL-MT3000 WiFi 6 Router,Network,1,,69.00,69.00,https://www.gl-inet.com/products/gl-mt3000/
|
||||
HUB,BAT1_HUB,Anker 20000mAh USB-C PowerBank A1287,Power,3,,25.00,75.00,https://www.anker.com/products/a1287
|
||||
HUB,SPEAKER_HUB,JBL Go 3 Bluetooth Speaker,Audio,1,,29.00,29.00,https://www.jbl.com/bluetooth-speakers/JBL+GO+3.html
|
||||
HUB,LED_STRIP_HUB,USB LED Strip 3m Warm White,Lighting,1,,8.00,8.00,Ambient lighting for suitcase
|
||||
HUB,CABLE_USB_HUB,USB-C Cable 1m Ugreen (Qty 10),Power,10,,3.00,30.00,All puzzle power cables
|
||||
HUB,SUITCASE_1,Suitcase Rimowa 57L equivalent ABS,Mechanical,1,,45.00,45.00,Main hub suitcase — rigid
|
||||
HUB,SUITCASE_2,Suitcase 42L softcase,Mechanical,1,,25.00,25.00,Puzzle objects suitcase A
|
||||
HUB,SUITCASE_3,Suitcase 42L softcase,Mechanical,1,,25.00,25.00,Puzzle objects suitcase B
|
||||
HUB,FOAM_CUT,EVA Foam sheets 10mm (for suitcase cutouts),Mechanical,3,,6.00,18.00,Custom foam per suitcase
|
||||
HUB,SIGNAGE,Printed signage A4 laminated (puzzle labels),Print,10,,0.50,5.00,Puzzle identification labels
|
||||
HUB,MORSE_REF,Morse code reference card A6 laminated,Print,10,,0.20,2.00,Hint card for P5
|
||||
HUB,PCB_HUB,RTC_PHONE Custom PCB JLCPCB,PCB,1,,8.00,8.00,4-layer — SLIC + ESP32 integration
|
||||
,,,,,,,,
|
||||
SUMMARY,Total Puzzle Electronics,,,,,,,207.00
|
||||
SUMMARY,Total Hub & Infrastructure,,,,,,,390.00
|
||||
SUMMARY,Total Enclosures & Mechanical,,,,,,,130.00
|
||||
SUMMARY,Total Kit,,,,,,,727.00
|
||||
|
@@ -0,0 +1,315 @@
|
||||
# Guide d'Assemblage et Checklist Qualité — Kit V3
|
||||
|
||||
> Dernière mise à jour : 2026-04-03
|
||||
> Temps estimé (premier assemblage) : 8-12 heures
|
||||
> Temps estimé (assemblage répété, kit connu) : 3-4 heures
|
||||
|
||||
---
|
||||
|
||||
## Prérequis
|
||||
|
||||
### Outillage
|
||||
- Fer à souder 50 W + étain 0,8 mm
|
||||
- Multimètre (continuité + tension)
|
||||
- Pistolet à colle thermique
|
||||
- Tournevis Torx T8 + cruciforme PH1
|
||||
- Pince coupante, pince à dénuder
|
||||
- Imprimante 3D (PLA/ABS pour boîtiers)
|
||||
- Découpeuse laser (optionnel — tablette P6 peut être achetée)
|
||||
|
||||
### Logiciels requis sur le poste d'assemblage
|
||||
- PlatformIO CLI (`pip install platformio`)
|
||||
- Python 3.11+
|
||||
- `esptool.py` (`pip install esptool`)
|
||||
- `idf.py` (ESP-IDF v5.x pour ESP32-S3-BOX-3)
|
||||
|
||||
---
|
||||
|
||||
## Étape 1 : Impression 3D des Boîtiers (2-4 h)
|
||||
|
||||
| Boîtier | Fichier STL | Matière | Paramètres |
|
||||
|---------|------------|---------|-----------|
|
||||
| P1 Boîte Sonore | `hardware/enclosure/p1_son/p1_son_box.stl` | PLA noir | 0,2 mm, 20% infill |
|
||||
| P2 Support Breadboard | `hardware/enclosure/p2_circuit/p2_frame.stl` | PLA gris | 0,2 mm, 15% infill |
|
||||
| P4 Poste Radio | `hardware/enclosure/p4_radio/p4_radio_retro.stl` | PLA marron | 0,2 mm, 30% infill |
|
||||
| P5 Télégraphe base | `hardware/enclosure/p5_morse/p5_base.stl` | PLA bois (PHA) | 0,15 mm, 25% infill |
|
||||
| P7 Coffre | `hardware/enclosure/p7_coffre/p7_coffre_body.stl` | ABS noir | 0,2 mm, 40% infill |
|
||||
|
||||
- [ ] Tous les boîtiers imprimés sans warping
|
||||
- [ ] Trous de fixation propres (percer si nécessaire : M3 pour PCB, M2 pour servo)
|
||||
- [ ] Post-traitement : ponçage 120→240 grains si besoin esthétique
|
||||
|
||||
---
|
||||
|
||||
## Étape 2 : Assemblage Électronique par Puzzle
|
||||
|
||||
### P1 — Boîte Sonore (Séquence musicale)
|
||||
|
||||
**Composants :**
|
||||
- ESP32-S3 DevKit × 1
|
||||
- Haut-parleur 8 Ω 1 W × 1
|
||||
- MAX98357A (I2S amplificateur) × 1
|
||||
- Boutons arcade 30 mm (rouge, bleu, jaune, vert) × 4
|
||||
- LED 5 mm (couleurs correspondantes) × 4
|
||||
- Résistances 220 Ω × 4
|
||||
|
||||
**Câblage :**
|
||||
```
|
||||
ESP32-S3 GPIO4 → BCLK (MAX98357A)
|
||||
ESP32-S3 GPIO5 → LRCLK
|
||||
ESP32-S3 GPIO6 → DIN
|
||||
GPIO 12/13/14/15 → Boutons (INPUT_PULLUP)
|
||||
GPIO 16/17/18/19 → LEDs via 220 Ω
|
||||
```
|
||||
|
||||
- [ ] Souder MAX98357A sur mini-PCB stripboard
|
||||
- [ ] Tester amplificateur seul (multimètre sur sortie : ~5V idle)
|
||||
- [ ] Coller haut-parleur avec colle thermique
|
||||
- [ ] Insérer boutons arcade dans les 4 trous du boîtier
|
||||
- [ ] Câbler selon schéma `hardware/wiring/wiring.md`
|
||||
- [ ] Test continuité toutes les connexions
|
||||
|
||||
### P2 — Breadboard Magnétique (Circuit LED)
|
||||
|
||||
**Composants :**
|
||||
- ESP32 DevKit v1 × 1
|
||||
- Reed switches × 8 (détection positionnement composants)
|
||||
- Magnets néodyme Ø5mm × 8 (intégrés aux composants pédagogiques)
|
||||
- LED 5 mm rouge × 1
|
||||
- Panneau support MDF 60×40 cm (découpé laser)
|
||||
|
||||
**Câblage :**
|
||||
```
|
||||
GPIO 12-19 → Reed switches (INPUT_PULLUP)
|
||||
GPIO 21 → LED témoin résolution
|
||||
```
|
||||
|
||||
- [ ] Coller reed switches sous le panneau MDF aux positions définies
|
||||
- [ ] Coller magnets dans les embases composants pédagogiques
|
||||
- [ ] Câbler ESP32 et tester chaque reed switch individuellement
|
||||
- [ ] Vérifier que la combinaison correcte allume LED GPIO21
|
||||
|
||||
### P4 — Poste Radio (Fréquence)
|
||||
|
||||
**Composants :**
|
||||
- ESP32 DevKit v1 × 1
|
||||
- Encodeur rotatif KY-040 × 1
|
||||
- Écran OLED 128×64 SSD1306 I2C × 1
|
||||
- 18650 Li-Ion + support + circuit de charge TP4056 × 1
|
||||
|
||||
**Câblage :**
|
||||
```
|
||||
Encodeur CLK → GPIO 32
|
||||
Encodeur DT → GPIO 33
|
||||
Encodeur SW → GPIO 25
|
||||
SSD1306 SDA → GPIO 21
|
||||
SSD1306 SCL → GPIO 22
|
||||
TP4056 OUT → VIN ESP32
|
||||
```
|
||||
|
||||
- [ ] Souder support 18650 + TP4056 sur veroboard
|
||||
- [ ] Monter encodeur sur face avant boîtier rétro
|
||||
- [ ] Coller OLED sur fenêtre boîtier (cadre époxy)
|
||||
- [ ] Charger 18650 à 100% via USB-C avant test
|
||||
|
||||
### P5 — Télégraphe Morse
|
||||
|
||||
**Composants :**
|
||||
- ESP32 DevKit v1 × 1
|
||||
- Bouton télégraphe bois + laiton (artisanal ou impression 3D) × 1
|
||||
- Buzzer actif 5V × 1
|
||||
- LED ambre 5 mm × 1 (signal visuel mode non-tech)
|
||||
|
||||
**Câblage :**
|
||||
```
|
||||
Bouton télégraphe → GPIO 12 (INPUT_PULLUP, ACTIVE LOW)
|
||||
Buzzer → GPIO 13 (via transistor NPN 2N2222)
|
||||
LED ambre → GPIO 14 (via 220 Ω)
|
||||
```
|
||||
|
||||
- [ ] Assembler mécanisme télégraphe (ressort de rappel, laiton contact)
|
||||
- [ ] Tester durée de vie contact (appuyer 100× : doit être fiable)
|
||||
- [ ] Régler seuil point/tiret dans firmware (défaut : 150 ms)
|
||||
|
||||
### P6 — Tablette Symboles Alchimiques (NFC)
|
||||
|
||||
**Composants :**
|
||||
- ESP32 DevKit v1 × 1
|
||||
- Module NFC PN532 I2C × 1
|
||||
- NTAG213 (autocollant NFC) × 12
|
||||
- Tablette bois découpée laser 30×20 cm × 1
|
||||
- Emplacements gravés laser × 12
|
||||
|
||||
**Câblage :**
|
||||
```
|
||||
PN532 SDA → GPIO 21
|
||||
PN532 SCL → GPIO 22
|
||||
PN532 IRQ → GPIO 19 (interruption lecture NFC)
|
||||
```
|
||||
|
||||
- [ ] Programmer les 12 UIDs NTAG213 (voir section NVS ci-dessous)
|
||||
- [ ] Coller symboles alchimiques sur les tuiles NTAG213
|
||||
- [ ] Tester lecture NFC de chaque tuile (distance max : 3 cm)
|
||||
- [ ] Graver les 12 emplacements sur la tablette (laser ou Dremel)
|
||||
|
||||
### P7 — Coffre Final (Verrou électronique)
|
||||
|
||||
**Composants :**
|
||||
- ESP32 DevKit v1 × 1
|
||||
- Clavier matriciel 4×4 × 1
|
||||
- Servo SG90 × 1 (verrou mécanique)
|
||||
- OLED 128×32 SSD1306 × 1
|
||||
- Buzzer passif × 1
|
||||
- Coffre en bois 25×20×15 cm × 1
|
||||
|
||||
**Câblage :**
|
||||
```
|
||||
Clavier lignes → GPIO 12/13/14/15
|
||||
Clavier colonnes→ GPIO 16/17/18/19
|
||||
Servo signal → GPIO 23 (PWM)
|
||||
OLED SDA → GPIO 21
|
||||
OLED SCL → GPIO 22
|
||||
Buzzer → GPIO 25
|
||||
```
|
||||
|
||||
- [ ] Tester servo (0°=fermé, 90°=ouvert)
|
||||
- [ ] Ajuster mécanisme de verrou (doit tenir sans alimentation — ressort)
|
||||
- [ ] Tester clavier matriciel (toutes les 16 touches)
|
||||
|
||||
---
|
||||
|
||||
## Étape 3 : Flash Firmware par Puzzle (PlatformIO)
|
||||
|
||||
### Prérequis
|
||||
|
||||
```bash
|
||||
# Cloner le repo et les submodules
|
||||
git clone --recurse-submodules https://github.com/electron-rare/le-mystere-professeur-zacus.git
|
||||
cd le-mystere-professeur-zacus/ESP32_ZACUS
|
||||
|
||||
# Vérifier que PlatformIO reconnaît les environnements
|
||||
pio project config
|
||||
```
|
||||
|
||||
### Flash par environnement
|
||||
|
||||
```bash
|
||||
# P1 — Boîte Sonore
|
||||
pio run -e p1_son --target upload --upload-port /dev/ttyUSB0
|
||||
|
||||
# P2 — Breadboard Magnétique
|
||||
pio run -e p2_circuit --target upload --upload-port /dev/ttyUSB0
|
||||
|
||||
# P4 — Poste Radio
|
||||
pio run -e p4_radio --target upload --upload-port /dev/ttyUSB0
|
||||
|
||||
# P5 — Télégraphe Morse
|
||||
pio run -e p5_morse --target upload --upload-port /dev/ttyUSB0
|
||||
|
||||
# P6 — Tablette Symboles
|
||||
pio run -e p6_symboles --target upload --upload-port /dev/ttyUSB0
|
||||
|
||||
# P7 — Coffre Final
|
||||
pio run -e p7_coffre --target upload --upload-port /dev/ttyUSB0
|
||||
|
||||
# BOX-3 (ESP32-S3 — utiliser idf.py)
|
||||
cd ui_freenove_allinone
|
||||
idf.py -p /dev/ttyUSB0 flash monitor
|
||||
```
|
||||
|
||||
> **Note :** sur macOS, remplacer `/dev/ttyUSB0` par `/dev/cu.usbserial-XXXX` (vérifier avec `ls /dev/cu.*`).
|
||||
|
||||
### Vérification post-flash
|
||||
|
||||
```bash
|
||||
# Monitor série pour vérifier le boot
|
||||
pio device monitor -e p1_son --baud 115200
|
||||
|
||||
# Attendu dans les logs :
|
||||
# [BOOT] P1_SON v3.1 — ESP-NOW MAC: AA:BB:CC:DD:EE:FF
|
||||
# [ESPNOW] Waiting for BOX-3 master...
|
||||
```
|
||||
|
||||
- [ ] P1 : boot OK, MAC affiché, LED clignotante (attente mesh)
|
||||
- [ ] P2 : boot OK, reed switches testés (log: `REED[0..7]: 00000000`)
|
||||
- [ ] P4 : boot OK, OLED affiche `FREQ: 800 Hz`, encodeur réactif
|
||||
- [ ] P5 : boot OK, mode détecté (`TECH` ou `NON_TECH` selon profil)
|
||||
- [ ] P6 : boot OK, NFC prêt (`PN532: READY, 12 UIDs loaded`)
|
||||
- [ ] P7 : boot OK, OLED `LOCKED`, clavier actif, servo en position 0°
|
||||
|
||||
---
|
||||
|
||||
## Étape 4 : Configuration NVS (Non-Volatile Storage)
|
||||
|
||||
Le NVS stocke les paramètres persistants de chaque puzzle : UIDs NFC, credentials WiFi, paramètres maître du jeu.
|
||||
|
||||
### Configuration NFC (P6)
|
||||
|
||||
```bash
|
||||
# Programmer les UIDs des 12 tuiles NTAG213
|
||||
# Créer le fichier nvs_p6.csv :
|
||||
cat > /tmp/nvs_p6.csv << 'EOF'
|
||||
key,type,encoding,value
|
||||
nfc_uid_01,data,hex,04A1B2C3D4E500
|
||||
nfc_uid_02,data,hex,04B2C3D4E5F600
|
||||
# ... (12 UIDs au total)
|
||||
nfc_correct_order,data,string,"7,2,11,4,9,1,8,3,12,6,10,5"
|
||||
EOF
|
||||
|
||||
# Flash NVS partition
|
||||
python3 $IDF_PATH/components/nvs_flash/nvs_partition_generator/nvs_partition_gen.py \
|
||||
generate /tmp/nvs_p6.csv /tmp/nvs_p6.bin 0x6000
|
||||
|
||||
esptool.py -p /dev/ttyUSB0 write_flash 0x9000 /tmp/nvs_p6.bin
|
||||
```
|
||||
|
||||
### Configuration WiFi (tous les ESP32)
|
||||
|
||||
```bash
|
||||
# Méthode simple : via serial monitor au premier démarrage
|
||||
# Sur le terminal PlatformIO monitor, envoyer :
|
||||
WIFI_SSID=ZacusVenue2026
|
||||
WIFI_PASS=motdepasse_lieu
|
||||
|
||||
# Ou via nvs_partition_gen pour le déploiement en masse :
|
||||
cat > /tmp/nvs_wifi.csv << 'EOF'
|
||||
key,type,encoding,value
|
||||
wifi_ssid,data,string,ZacusVenue2026
|
||||
wifi_pass,data,string,motdepasse_lieu
|
||||
EOF
|
||||
```
|
||||
|
||||
### Paramètres Maître du Jeu (BOX-3)
|
||||
|
||||
Sur l'interface tactile BOX-3 au premier démarrage :
|
||||
- `Settings → Game Master PIN` : définir un PIN à 4 chiffres (accès réglages en jeu)
|
||||
- `Settings → Default Duration` : 60 min (modifiable à chaque session)
|
||||
- `Settings → XTTS Host` : `kxkm-ai:5002` (ou laisser vide pour Piper/SD fallback)
|
||||
- `Settings → Piper Host` : `192.168.0.120:8001`
|
||||
|
||||
---
|
||||
|
||||
## Étape 5 : Checklist Qualité Avant Première Utilisation
|
||||
|
||||
### Test fonctionnel complet (durée : ~30 min)
|
||||
|
||||
- [ ] **Allumer tout le kit** — toutes batteries connectées, attendre 60 secondes
|
||||
- [ ] **Mesh ESP-NOW** — BOX-3 affiche `MESH: 6/6 nodes connected`
|
||||
- [ ] **P1 Séquence Sonore** — Mélodie jouée, appuyer séquence correcte → signal OK envoyé au BOX-3
|
||||
- [ ] **P2 Circuit** — Placer composants en position correcte → LED GPIO21 allumée → BOX-3 reçoit `P2_SOLVED`
|
||||
- [ ] **P3 QR Treasure** — Scanner les 6 QR dans l'ordre → BOX-3 valide `P3_SOLVED`
|
||||
- [ ] **P4 Radio** — Tourner à 1337 Hz → BOX-3 reçoit `P4_SOLVED`
|
||||
- [ ] **P5 Morse** — Taper ZACUS en morse → BOX-3 reçoit `P5_SOLVED`
|
||||
- [ ] **P6 Symboles** — Placer 12 tuiles dans l'ordre correct → BOX-3 reçoit `P6_SOLVED`
|
||||
- [ ] **P7 Coffre** — Entrer le code 8 chiffres assemblé → servo tourne à 90° → coffre ouvert
|
||||
- [ ] **NPC Audio** — Le Professeur Zacus parle à chaque étape clé (tester avec `./tools/dev/zacus.sh voice-bridge test`)
|
||||
- [ ] **Scoring** — Fin de partie → BOX-3 affiche score et diplôme PDF généré
|
||||
|
||||
### Checklist esthétique et robustesse
|
||||
|
||||
- [ ] Aucun câble visible ou dangereux (tout cheminé + fermé)
|
||||
- [ ] Boîtiers vissés ou clipsés — aucune pièce qui s'ouvre involontairement
|
||||
- [ ] Toutes les LEDs et écrans visibles depuis la position joueur normale
|
||||
- [ ] Texte imprimé lisible (taille min 14 pt à distance de bras)
|
||||
- [ ] Logo et habillage thématique ("Laboratoire du Pr. Zacus") appliqués
|
||||
- [ ] Kit de secours validé : code maître P7 fonctionne, fiche lamifiée présente
|
||||
@@ -0,0 +1,200 @@
|
||||
# 3D Enclosure Specifications — Zacus V3
|
||||
|
||||
All enclosures designed for FDM 3D printing unless noted. STL source files live in `hardware/enclosures/`.
|
||||
OpenSCAD source: `hardware/enclosures/*.scad` (see plan Task 2.3 for parametric source).
|
||||
|
||||
---
|
||||
|
||||
## P1 — Boîte Sonore (Séquence Sonore)
|
||||
|
||||
**Dimensions:** 150 × 100 × 80 mm (L × W × H)
|
||||
**Material:** PETG (preferred for durability) or PLA+
|
||||
**Color:** Black — for stage/theatrical look; buttons are colored by their caps
|
||||
|
||||
### Openings & Cutouts
|
||||
|
||||
| Feature | Location | Size | Notes |
|
||||
|------------------|------------------------|---------------|----------------------------------------------|
|
||||
| Speaker grille | Front face, upper half | 40 × 40 mm | Honeycomb pattern — 2mm hex holes, 1mm walls |
|
||||
| Button hole RED | Top face, x=20 | ⌀30 mm | Sanwa OBSF-30 standard mounting |
|
||||
| Button hole BLUE | Top face, x=60 | ⌀30 mm | Sanwa OBSF-30 |
|
||||
| Button hole YEL | Top face, x=90 | ⌀30 mm | Sanwa OBSF-30 |
|
||||
| Button hole GRN | Top face, x=130 | ⌀30 mm | Sanwa OBSF-30 |
|
||||
| USB-C slot | Right side, bottom | 12 × 6 mm | Power input access |
|
||||
| LED strip window | Front face, lower strip| 100 × 8 mm | Diffused acrylic insert (frosted tape ok) |
|
||||
| Lid vent grid | Lid top | 4×1 mm slots | 10mm grid — passive thermal |
|
||||
| PCB mount studs | Interior floor | M3 standoffs | 4× M3×5mm brass inserts |
|
||||
|
||||
### Assembly
|
||||
- Box body + removable lid (M3 screws, 4 corners)
|
||||
- Speaker mounted on interior face panel with 2× M2.5 self-tappers
|
||||
- MAX98357A module hot-glued behind speaker grille
|
||||
- ESP32-S3 DevKit velcro-mounted to floor (removable for flashing)
|
||||
- Buttons snap into top holes with Sanwa mounting nut
|
||||
|
||||
### Print Settings
|
||||
| Parameter | Value |
|
||||
|-----------------|------------------------|
|
||||
| Layer height | 0.2 mm |
|
||||
| Infill | 25% gyroid |
|
||||
| Walls | 3 perimeters |
|
||||
| Top/bottom | 4 layers |
|
||||
| Supports | Yes — lid vent overhang only |
|
||||
| Print time est. | ~5h (box) + 1h (lid) |
|
||||
| Filament | ~180g PETG |
|
||||
|
||||
---
|
||||
|
||||
## P4 — Poste Radio Rétro (Fréquence Radio)
|
||||
|
||||
**Dimensions:** 200 × 150 × 120 mm (L × W × H)
|
||||
**Material:** PLA (retro aesthetic — easier to sand and paint)
|
||||
**Color:** Print in dark brown or beige PLA; hand-paint or vinyl wrap for retro finish
|
||||
|
||||
### Openings & Cutouts
|
||||
|
||||
| Feature | Location | Size | Notes |
|
||||
|---------------------|------------------------------|-----------------|-----------------------------------------------|
|
||||
| Rotary encoder hole | Front face, right side | ⌀14 mm | KY-040 shaft + nut clearance |
|
||||
| Encoder knob recess | Front face, around encoder | ⌀35 mm, 5mm deep| For large Bakelite-style knob |
|
||||
| OLED window | Front face, left-center | 35 × 18 mm | Acrylic window insert — glued from inside |
|
||||
| Speaker grille | Front face, upper center | ⌀80 mm (circular)| 19-hole hex pattern — retro radio aesthetic |
|
||||
| USB-C slot | Bottom face | 12 × 6 mm | Recessed — hidden when on table |
|
||||
| Battery access door | Back face | 60 × 30 mm | Sliding door for 18650 replacement |
|
||||
| Antenna cutout | Top face, right side | 8 × 60 mm slot | Decorative antenna rod (wooden dowel) |
|
||||
| Corner radius | All edges | R15 mm | Rounded hull — retro form factor |
|
||||
| Vent holes | Back face, bottom | 4 × 8mm slots | Battery charging ventilation |
|
||||
|
||||
### Assembly
|
||||
- Body printed in 2 halves (top + bottom) — split at midline
|
||||
- OLED glued behind acrylic window from inside
|
||||
- KY-040 mounted through front panel, locked with hex nut
|
||||
- Speaker front-mounted, MAX98357A behind grille
|
||||
- 18650 holder slides into battery compartment from back door
|
||||
- ESP32 DevKit mounted on standoffs — accessible from bottom removal
|
||||
|
||||
### Print Settings
|
||||
| Parameter | Value |
|
||||
|-----------------|--------------------------|
|
||||
| Layer height | 0.2 mm (0.15 for details)|
|
||||
| Infill | 20% gyroid |
|
||||
| Walls | 4 perimeters |
|
||||
| Top/bottom | 5 layers |
|
||||
| Supports | Yes — rounded hull corners, antenna slot |
|
||||
| Print time est. | ~9h (body halves) + 1h (door) |
|
||||
| Filament | ~320g PLA |
|
||||
| Post-process | Sand 220 grit → prime → paint (retro brown/beige) |
|
||||
|
||||
---
|
||||
|
||||
## P5 — Télégraphe (Code Morse)
|
||||
|
||||
**Dimensions:** 180 × 80 × 60 mm (L × W × H) — for base only; brass key mounted on top
|
||||
**Material:** PETG dark brown or woodPLA for telegraph aesthetic
|
||||
**Color:** Dark brown — telegraph base aesthetic
|
||||
|
||||
### Openings & Cutouts
|
||||
|
||||
| Feature | Location | Size | Notes |
|
||||
|---------------------|-----------------------|---------------|-------------------------------------------------|
|
||||
| Key mounting posts | Top face, right half | 2× M3 holes | Brass telegraph key bolted down |
|
||||
| Buzzer grille | Front face | 20 × 20 mm | Active buzzer — 2mm hex holes |
|
||||
| LED red (key press) | Front face, left | ⌀5 mm | 5mm LED holder press-fit |
|
||||
| LED green (valid) | Front face, right | ⌀5 mm | 5mm LED holder press-fit |
|
||||
| WS2812B window | Top face, left area | 15 × 8 mm | Diffused window for light-mode morse |
|
||||
| USB-C slot | Left side, bottom | 12 × 6 mm | Power input |
|
||||
| ESP32 access slot | Bottom face | 80 × 20 mm | Removable bottom panel for programming |
|
||||
| Cable routing slot | Interior | 6 mm channels | Wires from key to PCB |
|
||||
|
||||
### Assembly
|
||||
- Single-piece base with snap-on bottom panel
|
||||
- Brass telegraph key (Aliexpress morse key) mounted with M3 bolts
|
||||
- Key spring contact wired to GPIO4 with 2-pin JST connector
|
||||
- Buzzer hot-glued behind front grille
|
||||
- 5mm LED holders press-fit into front holes
|
||||
- WS2812B behind frosted diffuser window on top
|
||||
|
||||
### Print Settings
|
||||
| Parameter | Value |
|
||||
|-----------------|-------------------------|
|
||||
| Layer height | 0.2 mm |
|
||||
| Infill | 30% gyroid (heavier base)|
|
||||
| Walls | 4 perimeters |
|
||||
| Top/bottom | 4 layers |
|
||||
| Supports | Minimal — USB slot only |
|
||||
| Print time est. | ~4h |
|
||||
| Filament | ~130g PETG/woodPLA |
|
||||
|
||||
---
|
||||
|
||||
## P7 — Coffre Final (Coffre avec verrou électronique)
|
||||
|
||||
**Dimensions:** 250 × 200 × 150 mm (L × W × H) — wooden box, NOT 3D printed
|
||||
**Material:** Pine wood box (hardware store) — ESP32 electronics in internal tray (3D printed)
|
||||
|
||||
### Wooden Box Modifications
|
||||
|
||||
| Feature | Location | Size | Notes |
|
||||
|---------------------|-----------------------|-----------------|------------------------------------------------|
|
||||
| Keypad cutout | Lid front face | 75 × 60 mm | 4×3 membrane keypad recess |
|
||||
| OLED window | Lid front face | 35 × 18 mm | Above keypad — acrylic window |
|
||||
| RGB LED hole | Lid front face | ⌀8 mm | WS2812B status indicator |
|
||||
| Servo mount | Inside lid | 30 × 20 mm slot | SG90 servo + latch mechanism |
|
||||
| Latch slot | Front face (lid edge) | 15 × 8 mm | Steel latch bar — closes box electromagnetically|
|
||||
| USB-C slot | Back face | 12 × 6 mm | Hidden — covered by rubber flap |
|
||||
| Electronics tray | Inside base | 220 × 170 × 40 | 3D printed PETG tray — holds ESP32, PCB, servo |
|
||||
| Cable grommet | Tray → lid | ⌀12 mm | Spiral cable wrap — lid opening clearance |
|
||||
|
||||
### 3D Printed Electronics Tray (Internal)
|
||||
|
||||
**Dimensions:** 220 × 170 × 40 mm
|
||||
**Material:** PETG
|
||||
**Features:**
|
||||
- ESP32 DevKit mount (4× M3 standoffs)
|
||||
- Servo horn slot and cable routing
|
||||
- Buzzer pocket (press-fit)
|
||||
- PCB mount (2× M3)
|
||||
- Lid hinge clearance notch
|
||||
|
||||
### Print Settings (Electronics Tray)
|
||||
| Parameter | Value |
|
||||
|-----------------|--------------------|
|
||||
| Layer height | 0.25 mm |
|
||||
| Infill | 20% grid |
|
||||
| Walls | 3 perimeters |
|
||||
| Top/bottom | 3 layers |
|
||||
| Supports | None |
|
||||
| Print time est. | ~3h |
|
||||
| Filament | ~90g PETG |
|
||||
|
||||
### Servo Latch Mechanism
|
||||
- SG90 at 0° = latch bar extended (locked)
|
||||
- SG90 at 90° = latch bar retracted (open)
|
||||
- Latch bar: 3D printed PETG bar 60×8×5mm
|
||||
- Spring-loaded return (2mm steel wire spring)
|
||||
- PWM: 50Hz, duty 1ms=closed, 2ms=open
|
||||
|
||||
---
|
||||
|
||||
## P2, P3, P6 — No Custom Enclosure Needed
|
||||
|
||||
| Puzzle | Housing |
|
||||
|--------|-----------------------------------------------------|
|
||||
| P2 | Magnetic folding board (self-contained, no enclosure)|
|
||||
| P3 | Cards only — no enclosure |
|
||||
| P6 | Wooden tablet is the housing (laser cut 3mm plywood)|
|
||||
|
||||
---
|
||||
|
||||
## Print Summary
|
||||
|
||||
| Puzzle | Volume | Material | Print Time | Filament | Est. Cost |
|
||||
|--------|----------|----------|------------|----------|-----------|
|
||||
| P1 | 150×100×80 | PETG | ~6h | ~180g | ~5€ |
|
||||
| P4 | 200×150×120| PLA | ~10h | ~320g | ~9€ |
|
||||
| P5 | 180×80×60 | PETG | ~4h | ~130g | ~4€ |
|
||||
| P7 tray| 220×170×40 | PETG | ~3h | ~90g | ~3€ |
|
||||
| **Total** | | | **~23h** | **~720g**| **~21€** |
|
||||
|
||||
Filament cost based on ~0.03€/g PETG bulk spool.
|
||||
Wooden box for P7: ~12€ from hardware store.
|
||||
@@ -0,0 +1,347 @@
|
||||
# GPIO Mapping — Zacus V3 Puzzle Boards
|
||||
|
||||
All puzzle nodes run ESP-NOW slave firmware. Pinout must match `ESP32_ZACUS/puzzles/common/espnow_slave.h`.
|
||||
Master MAC address is hard-coded per production run and set via `espnow_slave_init()` at boot.
|
||||
|
||||
---
|
||||
|
||||
## P1 — Séquence Sonore (ESP32-S3-DevKitC-1-N16R8)
|
||||
|
||||
### GPIO Table
|
||||
|
||||
| GPIO | Function | Direction | Notes |
|
||||
|------|-------------------|-----------|------------------------------------------------|
|
||||
| 4 | I2S BCLK | OUT | MAX98357A bit clock |
|
||||
| 5 | I2S LRCLK (WS) | OUT | MAX98357A left/right clock |
|
||||
| 6 | I2S DIN | OUT | MAX98357A data in |
|
||||
| 7 | MAX98357A SD_MODE | OUT | HIGH = stereo / LOW = shutdown |
|
||||
| 8 | WS2812B DIN | OUT | 4-LED strip via SN74HCT125 level shifter |
|
||||
| 15 | Button RED | IN_PULLUP | Active LOW — 10kΩ external pull-up |
|
||||
| 16 | Button BLUE | IN_PULLUP | Active LOW — 10kΩ external pull-up |
|
||||
| 17 | Button YELLOW | IN_PULLUP | Active LOW — 10kΩ external pull-up |
|
||||
| 18 | Button GREEN | IN_PULLUP | Active LOW — 10kΩ external pull-up |
|
||||
|
||||
### Power Rails
|
||||
- 5V from USB-C → AMS1117-3.3 → ESP32 3V3 rail
|
||||
- 5V direct → MAX98357A VDD and WS2812B VCC
|
||||
|
||||
### Wiring Diagram
|
||||
|
||||
```
|
||||
USB-C 5V ─────┬──────────────────────────── MAX98357A VDD
|
||||
│ ┌── WS2812B VCC
|
||||
└── AMS1117-3.3 ── 3V3 ──┤── ESP32 3V3
|
||||
└── SN74HCT125 VCC
|
||||
|
||||
ESP32-S3
|
||||
GPIO4 ──────────────────────────────── MAX98357A BCLK
|
||||
GPIO5 ──────────────────────────────── MAX98357A LRC
|
||||
GPIO6 ──────────────────────────────── MAX98357A DIN
|
||||
GPIO7 ──── 3V3 (tied HIGH) ─────────── MAX98357A SD_MODE
|
||||
|
||||
GPIO8 ──── SN74HCT125 A1 ── Y1 ─────── WS2812B DIN (4 LEDs)
|
||||
|
||||
GPIO15 ──┬── 10kΩ ── 3V3
|
||||
└────────────────────────────── Arcade Button RED (NO → GND)
|
||||
|
||||
GPIO16 ──┬── 10kΩ ── 3V3
|
||||
└────────────────────────────── Arcade Button BLUE (NO → GND)
|
||||
|
||||
GPIO17 ──┬── 10kΩ ── 3V3
|
||||
└────────────────────────────── Arcade Button YELLOW (NO → GND)
|
||||
|
||||
GPIO18 ──┬── 10kΩ ── 3V3
|
||||
└────────────────────────────── Arcade Button GREEN (NO → GND)
|
||||
|
||||
MAX98357A OUTP/OUTN ─────────────────── Speaker 8Ω 2W
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## P2 — Circuit LED (ESP32-DevKitC-32E)
|
||||
|
||||
### GPIO Table
|
||||
|
||||
| GPIO | Function | Direction | Notes |
|
||||
|------|----------------------|-----------|----------------------------------------------|
|
||||
| 4 | Reed Switch 1 | IN_PULLUP | Magnetic sensor — 10kΩ external pull-up |
|
||||
| 5 | Reed Switch 2 | IN_PULLUP | Magnetic sensor — 10kΩ external pull-up |
|
||||
| 6 | Reed Switch 3 | IN_PULLUP | Magnetic sensor — 10kΩ external pull-up |
|
||||
| 7 | Reed Switch 4 | IN_PULLUP | Magnetic sensor — 10kΩ external pull-up |
|
||||
| 8 | Reed Switch 5 | IN_PULLUP | Magnetic sensor — 10kΩ external pull-up |
|
||||
| 9 | Reed Switch 6 | IN_PULLUP | Magnetic sensor — 10kΩ external pull-up |
|
||||
| 10 | Reed Switch 7 | IN_PULLUP | Magnetic sensor — 10kΩ external pull-up |
|
||||
| 11 | Reed Switch 8 | IN_PULLUP | Magnetic sensor — 10kΩ external pull-up |
|
||||
| 12 | WS2812B DIN Seg A | OUT | LED segment 1–2 via 74HCT125 |
|
||||
| 13 | WS2812B DIN Seg B | OUT | LED segment 3–4 via 74HCT125 |
|
||||
| 14 | WS2812B DIN Seg C | OUT | LED segment 5–6 via 74HCT125 |
|
||||
| 15 | WS2812B DIN Seg D | OUT | LED segment 7–8 via 74HCT125 |
|
||||
| 16 | WS2812B DIN Seg E | OUT | LED segment 9–10 via 74HCT125 |
|
||||
| 17 | WS2812B DIN Seg F | OUT | LED segment 11–12 via 74HCT125 |
|
||||
| 18 | WS2812B DIN Seg G | OUT | LED segment 13–14 via 74HCT125 |
|
||||
| 19 | WS2812B DIN Seg H | OUT | LED segment 15–16 via 74HCT125 |
|
||||
| 21 | Buzzer | OUT | NPN 2N2222 base via 1kΩ → active buzzer 5V |
|
||||
| 22 | Circuit Valid LED | OUT | Green LED via 470Ω series resistor |
|
||||
|
||||
### Wiring Diagram
|
||||
|
||||
```
|
||||
USB-C 5V ────┬───────────────────────────── WS2812B VCC (each segment)
|
||||
│ ┌── 1000µF bulk cap (5V decoupling)
|
||||
└── AMS1117-3.3 ── 3V3 ──── ESP32 3V3
|
||||
|
||||
ESP32
|
||||
GPIO4..11 ─── (each) ──┬── 10kΩ ── 3V3
|
||||
└───────────────── Reed Switch (NO → GND)
|
||||
|
||||
GPIO12..19 ─── SN74HCT125 Ax ── Yx ────── WS2812B DINx (2 LEDs each)
|
||||
|
||||
GPIO21 ──── 1kΩ ──── Q1(2N2222) base
|
||||
Q1 collector ─ Buzzer+ (5V)
|
||||
Q1 emitter ─ GND
|
||||
Buzzer- ─ GND
|
||||
|
||||
GPIO22 ──── 470Ω ──── LED Green+ ──── GND
|
||||
|
||||
Magnetic board (folding 60x40cm):
|
||||
Reed switches are embedded in the board surface
|
||||
Magnetic component pegs activate the reed switches when placed
|
||||
Board connector: 10-pin ribbon → ESP32 GPIO4..11 + 2x GND
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## P3 — QR Treasure (No dedicated electronics)
|
||||
|
||||
P3 uses the BOX-3 camera directly for QR scanning. No separate ESP32 board.
|
||||
|
||||
| Component | Notes |
|
||||
|-----------|-------|
|
||||
| 6× A5 laminated QR cards | Printed + laminated — QR content set at scenario compile time |
|
||||
| BOX-3 camera | ESP32-S3-BOX-3 integrated camera — handles scanning |
|
||||
|
||||
QR card placement order encoded in `game/scenarios/zacus_v3_complete.yaml` under `P3_QR.correct_order`.
|
||||
|
||||
---
|
||||
|
||||
## P4 — Fréquence Radio (ESP32-DevKitC-32E)
|
||||
|
||||
### GPIO Table
|
||||
|
||||
| GPIO | Function | Direction | Notes |
|
||||
|------|--------------------|-----------|--------------------------------------------------|
|
||||
| 4 | I2S BCLK | OUT | MAX98357A — radio static synthesis |
|
||||
| 5 | I2S LRCLK (WS) | OUT | MAX98357A |
|
||||
| 6 | I2S DIN | OUT | MAX98357A data |
|
||||
| 12 | Rotary Encoder DT | IN_PULLUP | KY-040 — direction detect |
|
||||
| 13 | Rotary Encoder SW | IN_PULLUP | KY-040 push button — confirm selection |
|
||||
| 14 | Rotary Encoder CLK | IN_PULLUP | KY-040 — clock signal |
|
||||
| 21 | I2C SDA (OLED) | BIDIR | SSD1306 128x64 — 4.7kΩ pull-up to 3V3 |
|
||||
| 22 | I2C SCL (OLED) | OUT | SSD1306 — 4.7kΩ pull-up to 3V3 |
|
||||
| 25 | DAC Output | OUT | RC low-pass → analog indicator LED (warm/cold) |
|
||||
|
||||
### Power Rails
|
||||
- 2× 18650 (2S) → TP4056 charger → MT3608 boost 5V → AMS1117-3.3 → 3V3
|
||||
- Alternatively: USB-C 5V input for bench/testing
|
||||
|
||||
### Wiring Diagram
|
||||
|
||||
```
|
||||
2x 18650 (2S = 7.4V nom) ── TP4056 ── MT3608 boost ── 5V rail
|
||||
5V rail ─────┬───────────────────────── MAX98357A VDD
|
||||
└── AMS1117-3.3 ── 3V3 ── ESP32 3V3
|
||||
|
||||
ESP32
|
||||
GPIO4 ──────────────────────────── MAX98357A BCLK
|
||||
GPIO5 ──────────────────────────── MAX98357A LRC
|
||||
GPIO6 ──────────────────────────── MAX98357A DIN
|
||||
MAX98357A OUTP/OUTN ─────────────── Speaker 8Ω 2W (in retro grille)
|
||||
|
||||
GPIO14 ──┬── 10kΩ ── 3V3
|
||||
└───────────────────────── KY-040 CLK
|
||||
GPIO12 ──┬── 10kΩ ── 3V3
|
||||
└───────────────────────── KY-040 DT
|
||||
GPIO13 ──┬── 10kΩ ── 3V3
|
||||
└───────────────────────── KY-040 SW (GND when pressed)
|
||||
|
||||
GPIO21 ──┬── 4.7kΩ ── 3V3
|
||||
└───────────────────────── SSD1306 SDA
|
||||
GPIO22 ──┬── 4.7kΩ ── 3V3
|
||||
└───────────────────────── SSD1306 SCL
|
||||
|
||||
GPIO25 ── 10kΩ ──┬── 100nF ── GND (RC low-pass filter)
|
||||
└────────────────── LED warm/cold indicator anode
|
||||
LED cathode ── 470Ω ── GND
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## P5 — Code Morse (ESP32-DevKitC-32E)
|
||||
|
||||
### GPIO Table
|
||||
|
||||
| GPIO | Function | Direction | Notes |
|
||||
|------|----------------------|-----------|------------------------------------------------|
|
||||
| 4 | Telegraph Key | IN_PULLUP | Brass key NO contact — 10kΩ external pull-up |
|
||||
| 5 | Buzzer | OUT | NPN 2N2222 base via 1kΩ — active buzzer 5V |
|
||||
| 6 | LED Red (key press) | OUT | Key pressed indicator — 470Ω series |
|
||||
| 7 | LED Green (validated)| OUT | Morse message valid — 470Ω series |
|
||||
| 8 | WS2812B (light mode) | OUT | 1 LED — NON_TECH visual morse pulses |
|
||||
|
||||
### Wiring Diagram
|
||||
|
||||
```
|
||||
USB-C 5V ────── AMS1117-3.3 ────── 3V3 ── ESP32 3V3
|
||||
|
||||
ESP32
|
||||
GPIO4 ──┬── 10kΩ ── 3V3
|
||||
└──────────────────────── Telegraph Key+ (brass contact)
|
||||
Telegraph Key- ───── GND
|
||||
|
||||
GPIO5 ──── 1kΩ ──── Q1(2N2222) base
|
||||
Q1 collector ── Buzzer+ ── 5V
|
||||
Q1 emitter ── GND
|
||||
Buzzer- ── GND
|
||||
|
||||
GPIO6 ──── 470Ω ──── LED Red anode ──── GND
|
||||
|
||||
GPIO7 ──── 470Ω ──── LED Green anode ── GND
|
||||
|
||||
GPIO8 ──────────────────────────── WS2812B DIN (1 LED, 5V)
|
||||
[3V3 → 5V: use SN74HCT125 or direct (WS2812B tolerates 3V3 DIN)]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## P6 — Symboles Alchimiques NFC (ESP32-DevKitC-32E)
|
||||
|
||||
### GPIO Table
|
||||
|
||||
| GPIO | Function | Direction | Notes |
|
||||
|------|-------------------|-----------|-----------------------------------------------|
|
||||
| 4 | MFRC522 RST | OUT | NFC module reset |
|
||||
| 5 | MFRC522 SDA (SS) | OUT | SPI chip select |
|
||||
| 18 | MFRC522 SCK | OUT | SPI clock |
|
||||
| 19 | MFRC522 MISO | IN | SPI data from MFRC522 |
|
||||
| 23 | MFRC522 MOSI | OUT | SPI data to MFRC522 |
|
||||
| 25 | Buzzer | OUT | NPN 2N2222 base via 1kΩ |
|
||||
| 26 | LED Green (valid) | OUT | Configuration valid — 470Ω |
|
||||
| 27 | LED Red (wrong) | OUT | Wrong placement — 470Ω |
|
||||
|
||||
### Wiring Diagram
|
||||
|
||||
```
|
||||
USB-C 5V ───── AMS1117-3.3 ───── 3V3 ── ESP32 3V3
|
||||
|
||||
ESP32
|
||||
GPIO5 (SDA/SS) ────────────── MFRC522 SDA
|
||||
GPIO18 (SCK) ────────────── MFRC522 SCK
|
||||
GPIO19 (MISO) ────────────── MFRC522 MISO
|
||||
GPIO23 (MOSI) ────────────── MFRC522 MOSI
|
||||
GPIO4 (RST) ────────────── MFRC522 RST
|
||||
3V3 ────────────── MFRC522 3.3V
|
||||
GND ────────────── MFRC522 GND
|
||||
[MFRC522 IRQ pin: not connected — polling mode]
|
||||
|
||||
GPIO25 ── 1kΩ ── Q1(2N2222) base
|
||||
Q1 collector ── Buzzer+ ── 5V
|
||||
Q1 emitter ── GND
|
||||
|
||||
GPIO26 ── 470Ω ── LED Green ── GND
|
||||
GPIO27 ── 470Ω ── LED Red ── GND
|
||||
|
||||
Wooden tablet (300x200mm, 3mm plywood):
|
||||
12 recessed slots, each slot has an NTAG213 sticker wired via flat cable
|
||||
to a multiplexed antenna array — OR single MFRC522 scans one tag at a time
|
||||
(player places one piece → holds it → scans → places next)
|
||||
Correct order: [7,2,11,4,9,1,8,3,12,6,10,5] (from zacus_v3_complete.yaml)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## P7 — Coffre Final (ESP32-DevKitC-32E)
|
||||
|
||||
### GPIO Table
|
||||
|
||||
| GPIO | Function | Direction | Notes |
|
||||
|------|----------------------|-----------|------------------------------------------------|
|
||||
| 4 | Keypad Row 1 | OUT | Matrix scan — driven LOW one at a time |
|
||||
| 5 | WS2812B RGB LED | OUT | Status LED — idle/entering/error/open |
|
||||
| 6 | Keypad Row 2 | OUT | Matrix scan |
|
||||
| 7 | Keypad Row 3 | OUT | Matrix scan |
|
||||
| 8 | Keypad Row 4 | OUT | Matrix scan |
|
||||
| 12 | Keypad Col 1 | IN_PULLUP | 10kΩ pull-up — reads HIGH unless row driven |
|
||||
| 13 | Keypad Col 2 | IN_PULLUP | 10kΩ pull-up |
|
||||
| 14 | Keypad Col 3 | IN_PULLUP | 10kΩ pull-up |
|
||||
| 18 | Buzzer | OUT | NPN 2N2222 base via 1kΩ |
|
||||
| 21 | I2C SDA (OLED) | BIDIR | SSD1306 128x64 — 4.7kΩ pull-up |
|
||||
| 22 | I2C SCL (OLED) | OUT | SSD1306 — 4.7kΩ pull-up |
|
||||
| 25 | SG90 Servo PWM | OUT | 50Hz PWM — 1ms=closed, 2ms=open |
|
||||
|
||||
### Wiring Diagram
|
||||
|
||||
```
|
||||
USB-C 5V ────┬─────────────────────────── SG90 Servo VCC (5V direct)
|
||||
│ ┌── WS2812B VCC
|
||||
└── AMS1117-3.3 ── 3V3 ── ESP32 3V3
|
||||
|
||||
ESP32
|
||||
GPIO4,6,7,8 ──────────────────────── Keypad Rows R1..R4
|
||||
(each scanned LOW sequentially, others HIGH-Z)
|
||||
|
||||
GPIO12 ──┬── 10kΩ ── 3V3 ────────── Keypad Col C1
|
||||
GPIO13 ──┬── 10kΩ ── 3V3 ────────── Keypad Col C2
|
||||
GPIO14 ──┬── 10kΩ ── 3V3 ────────── Keypad Col C3
|
||||
|
||||
GPIO21 ──┬── 4.7kΩ ── 3V3 ─────── SSD1306 SDA
|
||||
GPIO22 ──┬── 4.7kΩ ── 3V3 ─────── SSD1306 SCL
|
||||
|
||||
GPIO25 ─────────────────────────── SG90 Signal (orange wire)
|
||||
SG90 Red ── 5V
|
||||
SG90 Brown ── GND
|
||||
|
||||
GPIO5 ─────────────────────────── WS2812B DIN (1 LED)
|
||||
|
||||
GPIO18 ── 1kΩ ── Q1(2N2222) base
|
||||
Q1 collector ── Buzzer+ ── 5V
|
||||
Q1 emitter ── GND
|
||||
|
||||
Keypad 4x3 matrix layout:
|
||||
[1][2][3]
|
||||
[4][5][6]
|
||||
[7][8][9]
|
||||
[*][0][#]
|
||||
Rows: R1=top, R4=bottom
|
||||
Cols: C1=left, C3=right
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hub — BOX-3 + RTC_PHONE
|
||||
|
||||
### BOX-3 (ESP32-S3-BOX-3)
|
||||
|
||||
All BOX-3 GPIO is managed by Espressif BSP. Key internal connections:
|
||||
|
||||
| Resource | Notes |
|
||||
|------------------|----------------------------------------------------|
|
||||
| ESP-NOW Master | Coordinates all P1..P7 + RTC_PHONE via 2.4GHz |
|
||||
| Camera (GC0308) | QR code scanning for P3 |
|
||||
| Display (ILI9342)| Game master dashboard, scoring, puzzle status |
|
||||
| I2S Speaker | ES8311 audio codec — NPC voice output |
|
||||
| Mute button | GPIO0 — NPC voice mute toggle |
|
||||
| USB-C | Programming + 5V power from Anker powerbank |
|
||||
|
||||
### RTC_PHONE (Custom Board)
|
||||
|
||||
| GPIO/Pin | Function | Notes |
|
||||
|----------|--------------------|------------------------------------------|
|
||||
| GPIO4 | I2S BCLK → SLIC | Si3220 voice codec clock |
|
||||
| GPIO5 | I2S LRCLK → SLIC | Si3220 left/right clock |
|
||||
| GPIO6 | I2S DIN → SLIC | Audio data to SLIC (NPC voice) |
|
||||
| GPIO7 | I2S DOUT ← SLIC | Audio from handset (player speech) |
|
||||
| GPIO21 | I2C SDA → Si3220 | SLIC configuration |
|
||||
| GPIO22 | I2C SCL → Si3220 | SLIC clock |
|
||||
| GPIO18 | Hook switch detect | HIGH = handset lifted (interrupt) |
|
||||
| RJ11 | POTS line out | Standard telephone handset connector |
|
||||
| USB-C | 5V power | From Anker powerbank |
|
||||
@@ -0,0 +1,216 @@
|
||||
# Guide de Maintenance — Kit V3
|
||||
|
||||
> Dernière mise à jour : 2026-04-03
|
||||
> Fréquence : après chaque session (vérifications rapides) + annuelle (maintenance complète)
|
||||
|
||||
---
|
||||
|
||||
## Dépannage des Problèmes Courants
|
||||
|
||||
### Mesh ESP-NOW — Nœuds manquants
|
||||
|
||||
**Symptôme :** BOX-3 affiche `MESH: X/6 nodes connected` avec X < 6.
|
||||
|
||||
| Cause probable | Diagnostic | Solution |
|
||||
|----------------|-----------|---------|
|
||||
| ESP32 non alimenté | LED d'alimentation éteinte sur le puzzle | Vérifier câble USB-C + batterie |
|
||||
| ESP32 bloqué au boot | LED clignote rapidement sans s'arrêter | Appuyer bouton Reset (EN) sur le DevKit |
|
||||
| Hors de portée ESP-NOW (~200 m max) | Puzzle à plus de 20 m de BOX-3 | Rapprocher le puzzle ou repositionner |
|
||||
| Firmware corrompu | Log série : `Guru Meditation Error` | Re-flasher le firmware (voir section Update) |
|
||||
| Conflit MAC address | Log série : `ESPNOW: peer already exists` | Effacer NVS et redémarrer : `esptool.py erase_flash` |
|
||||
|
||||
**Commande de diagnostic rapide :**
|
||||
```bash
|
||||
# Afficher l'état du mesh depuis BOX-3 via serial
|
||||
pio device monitor -e box3 --baud 115200
|
||||
# Puis dans le menu tactile BOX-3 : Debug → ESP-NOW Status
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### P6 Tablette Symboles NFC — Tuile non détectée
|
||||
|
||||
**Symptôme :** Une tuile NFC posée sur son emplacement n'est pas reconnue.
|
||||
|
||||
| Cause | Diagnostic | Solution |
|
||||
|-------|-----------|---------|
|
||||
| Autocollant NFC endommagé | Tester la tuile avec un smartphone NFC | Remplacer l'autocollant NTAG213 (stock de rechange) |
|
||||
| Distance trop grande | Positionner la tuile directement sur l'encoche | Vérifier que le PN532 est bien collé sous la surface |
|
||||
| PN532 déconnecté | Log série : `PN532: INIT FAILED` | Vérifier câbles I2C SDA/SCL, re-souder si nécessaire |
|
||||
| UID non enregistré en NVS | Log série : `UNKNOWN UID: XXXXXXXX` | Re-programmer le NVS avec les UIDs corrects |
|
||||
|
||||
**Programmer un autocollant NTAG213 de remplacement :**
|
||||
```bash
|
||||
# Avec un smartphone Android + app NFC Tools
|
||||
# OU avec le reader PN532 + script Python :
|
||||
python3 tools/dev/nfc_program.py --uid-slot 7 --new-uid 04A1B2C3D4E500
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### P5 Télégraphe — Morse non détecté
|
||||
|
||||
**Symptôme :** Appuyer sur le télégraphe ne produit aucun effet.
|
||||
|
||||
| Cause | Solution |
|
||||
|-------|---------|
|
||||
| Contact oxydé (laiton) | Nettoyer les contacts avec papier abrasif 400 grain |
|
||||
| Ressort de rappel trop faible | Remplacer le ressort (diamètre 5 mm, force 0,3 N) |
|
||||
| Seuil point/tiret mal calibré | Ajuster `MORSE_DOT_MAX_MS` dans `firmware/p5_morse/config.h` |
|
||||
| GPIO en pull-up cassé | Mesurer tension GPIO au repos (doit être 3,3 V) |
|
||||
|
||||
---
|
||||
|
||||
### P7 Coffre — Servo bloqué ou ne tourne pas
|
||||
|
||||
**Symptôme :** Code correct entré mais le servo ne bouge pas (coffre reste fermé).
|
||||
|
||||
| Cause | Solution |
|
||||
|-------|---------|
|
||||
| Servo desserré mécaniquement | Vérifier vis de fixation bras de servo, re-coller si nécessaire |
|
||||
| Surcharge mécanique | Vérifier que le verrou n'est pas physiquement bloqué (dégager manuellement) |
|
||||
| Signal PWM incorrect | Mesurer GPIO 23 avec oscilloscope : 50 Hz, pulse 1-2 ms |
|
||||
| Servo défaillant | Remplacer le SG90 (prévoir 2 de rechange dans la valise) |
|
||||
|
||||
**Ouverture d'urgence (sans électronique) :**
|
||||
Utiliser la clé Allen M2 fournie dans la poche valise 3 pour retirer le bras de servo manuellement.
|
||||
|
||||
---
|
||||
|
||||
### BOX-3 — Écran noir au démarrage
|
||||
|
||||
**Diagnostic :**
|
||||
```bash
|
||||
# Vérifier le firmware via serial
|
||||
idf.py -p /dev/cu.usbserial-XXXX monitor
|
||||
# Log attendu : [BOOT] BOX3 v3.1 — Display init...
|
||||
```
|
||||
|
||||
| Cause | Solution |
|
||||
|-------|---------|
|
||||
| Firmware absent | Re-flasher : `idf.py -p /dev/cu.usbserial-XXXX flash` |
|
||||
| Batterie trop faible | Connecter au chargeur 5V/2A minimum avant démarrage |
|
||||
| Écran LCD déconnecté | Ouvrir le BOX-3 (4 vis Torx T8), vérifier nappe LCD |
|
||||
|
||||
---
|
||||
|
||||
### Voix NPC silencieuse
|
||||
|
||||
**Diagnostic par ordre de priorité :**
|
||||
|
||||
1. **Vérifier la connectivité WiFi** — BOX-3 : `Settings → WiFi Status`
|
||||
2. **Tester Piper TTS directement :**
|
||||
```bash
|
||||
curl -X POST http://192.168.0.120:8001/api/tts \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"text": "Test professeur Zacus", "voice": "tom-medium"}' \
|
||||
--output /tmp/test.wav && aplay /tmp/test.wav
|
||||
```
|
||||
3. **Mode offline** — Vérifier que la SD card est présente dans BOX-3 et contient le pool MP3 :
|
||||
```bash
|
||||
ls /sdcard/hotline_tts/ | wc -l # attendu : 50+ fichiers
|
||||
```
|
||||
4. **Régénérer le pool MP3 si absent :**
|
||||
```bash
|
||||
python3 tools/tts/generate_npc_pool.py --dry-run # vérifier sans générer
|
||||
python3 tools/tts/generate_npc_pool.py # générer
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Procédure de Mise à Jour Firmware
|
||||
|
||||
### Option 1 : Mise à jour OTA (Over-The-Air) — recommandée
|
||||
|
||||
```bash
|
||||
# Prérequis : BOX-3 et tous les puzzles sur le même réseau WiFi
|
||||
|
||||
# Build le firmware mis à jour
|
||||
cd ESP32_ZACUS
|
||||
pio run -e p1_son
|
||||
|
||||
# Upload OTA (l'ESP32 doit être en mode OTA_READY)
|
||||
# Depuis BOX-3 : Maintenance → OTA Update → Enable OTA on all nodes
|
||||
pio run -e p1_son --target upload --upload-protocol espota \
|
||||
--upload-port 192.168.1.XXX # IP de P1 affichée sur BOX-3
|
||||
```
|
||||
|
||||
### Option 2 : Mise à jour USB-C (physique)
|
||||
|
||||
```bash
|
||||
# Connecter le puzzle en USB-C au poste de développement
|
||||
pio run -e p1_son --target upload --upload-port /dev/cu.usbserial-XXXX
|
||||
|
||||
# BOX-3 (ESP32-S3)
|
||||
idf.py -p /dev/cu.usbserial-XXXX flash
|
||||
```
|
||||
|
||||
### Vérification post-mise à jour
|
||||
|
||||
```bash
|
||||
# Vérifier la version firmware
|
||||
pio device monitor -e p1_son --baud 115200
|
||||
# Attendu : [BOOT] P1_SON v3.X — ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Guide de Remplacement des Composants
|
||||
|
||||
### Composants consommables (à stocker en réserve)
|
||||
|
||||
| Composant | Quantité recommandée | Fournisseur | Prix unitaire |
|
||||
|-----------|---------------------|-------------|--------------|
|
||||
| NTAG213 autocollant NFC | 5 | AliExpress / Reichelt | ~0,30 € |
|
||||
| Servo SG90 | 2 | AliExpress | ~2 € |
|
||||
| ESP32 DevKit v1 | 1 | AliExpress / Mouser | ~5 € |
|
||||
| 18650 Li-Ion (P4) | 1 | NKON / Reichelt | ~4 € |
|
||||
| Bouton arcade 30 mm | 4 (jeu complet P1) | Aliexpress | ~1 € / pièce |
|
||||
| Câble USB-C 1 m | 3 | n'importe | ~3 € |
|
||||
| Ressort télégraphe (P5) | 3 | Bricomarché / quincaillerie | ~0,50 € |
|
||||
|
||||
### Remplacement d'un ESP32 DevKit
|
||||
|
||||
1. Déconnecter l'alimentation et tous les câbles.
|
||||
2. Dessouder ou déconnecter le connecteur JST si présent.
|
||||
3. Flasher le nouveau DevKit **avant** de l'installer (voir Étape 3 de l'assemblage).
|
||||
4. Re-programmer le NVS avec les UIDs et credentials du puzzle remplacé.
|
||||
5. Reconnecter et tester le mesh ESP-NOW.
|
||||
|
||||
### Remplacement d'une tuile NFC (P6)
|
||||
|
||||
1. Retirer l'autocollant NTAG213 de la tuile symbole (avec lame de rasoir, chauffer légèrement).
|
||||
2. Programmer le nouvel autocollant avec le même UID (script `tools/dev/nfc_program.py`).
|
||||
3. Coller le nouvel autocollant et vérifier la lecture (log : `NFC READ: UID match slot X`).
|
||||
|
||||
---
|
||||
|
||||
## Calendrier de Maintenance Annuelle
|
||||
|
||||
### Avant la saison (1× par an, avant les premières sessions)
|
||||
|
||||
| Tâche | Durée | Priorité |
|
||||
|-------|-------|---------|
|
||||
| Test complet de tous les puzzles (protocole étape 5) | 30 min | P0 |
|
||||
| Mise à jour firmware tous les ESP32 et BOX-3 | 45 min | P0 |
|
||||
| Vérification et remplacement des batteries dégradées | 15 min | P0 |
|
||||
| Nettoyage contacts électroniques (spray contact) | 15 min | P1 |
|
||||
| Vérification intégrité boîtiers (fissures, vis desserrées) | 10 min | P1 |
|
||||
| Régénération pool MP3 NPC (nouvelles phrases si besoin) | 20 min | P1 |
|
||||
| Vérification QR codes A5 (décoloration, plastification intacte) | 5 min | P2 |
|
||||
| Test servo P7 (1000 cycles ouvrir/fermer) | 10 min | P2 |
|
||||
| Mise à jour documentation si modifications hardware | — | P2 |
|
||||
|
||||
### Après 50 sessions (compteur dans BOX-3)
|
||||
|
||||
- Remplacer les 4 boutons arcade de P1 (usure mécanique prévisible).
|
||||
- Remplacer le servo SG90 de P7 (durée de vie : ~3000-5000 cycles).
|
||||
- Vérifier les contacts du télégraphe P5 (re-polir si nécessaire).
|
||||
- Inspecter le câblage interne de P6 (vibrations de transport).
|
||||
|
||||
### Log de maintenance
|
||||
|
||||
Tenir à jour le fichier `hardware/maintenance_log.csv` avec :
|
||||
```
|
||||
date,session_count,technician,actions_performed,components_replaced,notes
|
||||
```
|
||||
@@ -0,0 +1,115 @@
|
||||
# Gestion de l'Alimentation — Kit V3
|
||||
|
||||
> Dernière mise à jour : 2026-04-03
|
||||
> Concerne : 3 valises, 7 puzzles ESP32, RTC_PHONE, BOX-3, routeur GL.iNet
|
||||
|
||||
---
|
||||
|
||||
## Assignation des Batteries
|
||||
|
||||
| Batterie | Capacité | Appareils alimentés | Puissance totale | Autonomie estimée |
|
||||
|----------|----------|---------------------|-----------------|-------------------|
|
||||
| **Anker #1** (valise Hub) | 20 000 mAh / 5V | RTC_PHONE (0,5 W) + BOX-3 (2 W) + Routeur GL.iNet (3 W) | 5,5 W | ~3,5 h |
|
||||
| **Anker #2** (valise Hub, réserve) | 20 000 mAh / 5V | Chargeur USB multi-port puzzles (en session) ou secours Hub | — | — |
|
||||
| **Pack Puzzle A** | 10 000 mAh / 5V | P1 Boîte Sonore (1 W) + P2 Breadboard (2 W) + P4 Radio (interne 18650) | 3 W | ~8 h |
|
||||
| **Pack Puzzle B** | 10 000 mAh / 5V | P5 Télégraphe (0,5 W) + P6 Tablette Symboles (0,5 W) + P7 Coffre Final (1 W) | 2 W | ~15 h |
|
||||
|
||||
> **Note :** P3 QR Treasure utilise la caméra intégrée de BOX-3 (déjà compté dans Anker #1).
|
||||
> P4 intègre un 18650 rechargeable en interne — chargé via USB-C indépendamment.
|
||||
|
||||
---
|
||||
|
||||
## Autonomie par Appareil
|
||||
|
||||
| Appareil | Puissance moy. | Batterie | Autonomie calculée | Marge pour 2 sessions |
|
||||
|----------|---------------|----------|--------------------|-----------------------|
|
||||
| RTC_PHONE | 0,5 W | Anker #1 | >10 h | Oui |
|
||||
| BOX-3 (ESP32-S3-BOX-3) | 2 W | Anker #1 | ~5 h | Non (recharge entre sessions) |
|
||||
| Routeur GL.iNet GL-MT3000 | 3 W | Anker #1 | ~3,5 h | Non (1 session) |
|
||||
| P1 Boîte Sonore | 1 W | Pack A | ~12 h | Oui |
|
||||
| P2 Breadboard Magnétique | 2 W (pics) | Pack A | ~6 h | Oui |
|
||||
| P4 Poste Radio | 18650 interne | — | ~4 h | Vérifier avant chaque session |
|
||||
| P5 Télégraphe | 0,5 W | Pack B | >20 h | Oui |
|
||||
| P6 Tablette Symboles | 0,5 W | Pack B | >20 h | Oui |
|
||||
| P7 Coffre Final | 1 W (+ servo) | Pack B | ~10 h | Oui |
|
||||
|
||||
---
|
||||
|
||||
## Procédure de Recharge entre Sessions
|
||||
|
||||
### Séquence optimale (chargeur GaN 65 W, 4 ports)
|
||||
|
||||
```
|
||||
Port 1 (PD 45W) → Anker #1 (20 000 mAh) — Temps : ~2h00
|
||||
Port 2 (PD 18W) → Anker #2 (20 000 mAh) — Temps : ~3h30
|
||||
Port 3 (PD 18W) → Pack Puzzle A (10 000 mAh) — Temps : ~1h30
|
||||
Port 4 (5W) → P4 Radio via USB-C interne — Temps : ~1h30
|
||||
```
|
||||
|
||||
> Commencer immédiatement après la session pour avoir tout chargé avant la session suivante.
|
||||
|
||||
### Règles de gestion
|
||||
|
||||
1. **Ne jamais stocker une batterie déchargée** — endommageur des cellules Li-Ion.
|
||||
2. **Recharger à 100% avant chaque déploiement**, pas seulement à "suffisant".
|
||||
3. **Vérifier le niveau avant d'emballer** — toutes les LEDs de niveau doivent montrer 4/4.
|
||||
4. **Pack B peut alimenter 2 sessions consécutives** sans recharge intermédiaire (autonomie 15 h+).
|
||||
5. **Anker #1 nécessite une recharge entre sessions** si deux sessions le même jour.
|
||||
|
||||
### Matériel de recharge (à emporter dans la valise 1)
|
||||
|
||||
- [ ] Chargeur GaN 65 W, 4 ports USB-C (Anker 737 ou équivalent)
|
||||
- [ ] 5× câbles USB-C courts (0,5 m) pour la recharge simultanée
|
||||
- [ ] Étiquettes indiquant le port assigné à chaque batterie
|
||||
|
||||
---
|
||||
|
||||
## Alimentation d'Urgence (batterie morte en cours de partie)
|
||||
|
||||
### Procédure selon l'appareil défaillant
|
||||
|
||||
#### Hub mort (Anker #1 vide) — impact critique
|
||||
|
||||
**Symptômes :** BOX-3 éteint, RTC_PHONE silencieux, routeur éteint.
|
||||
|
||||
**Action immédiate :**
|
||||
1. Brancher Anker #2 (réserve) sur le splitter Hub.
|
||||
2. Temps de redémarrage : ~30 secondes.
|
||||
3. Annoncer aux joueurs (en voix de maître du jeu) : *"Le Professeur a eu un petit problème technique…"*
|
||||
4. Sur BOX-3 : `Settings → Restore Session` — reprend là où le jeu s'est arrêté.
|
||||
|
||||
#### Puzzle mort (Pack A ou B vide) — impact partiel
|
||||
|
||||
**Symptômes :** Un ou plusieurs puzzles ESP32 ne répondent plus sur le mesh.
|
||||
|
||||
**Action :**
|
||||
1. Connecter ce puzzle sur le chargeur GaN (Port 3 ou 4) — le puzzle redémarre en quelques secondes.
|
||||
2. Sur BOX-3 : le nœud manquant réapparaît dans `MESH: X/6 nodes`.
|
||||
3. Si un nœud ne revient pas après 2 min : `Reset Hardware` sur BOX-3 → envoie une commande de reset ESP-NOW à tous les nœuds.
|
||||
|
||||
#### P4 Radio (18650 interne) mort
|
||||
|
||||
**Action :** Brancher USB-C sur n'importe quel port disponible pendant le jeu — charge et fonctionne simultanément.
|
||||
|
||||
#### Cas extrême : panne totale irréparable
|
||||
|
||||
1. Passer en mode "maître du jeu manuel" : lire les énigmes depuis le kit papier (valise 3).
|
||||
2. Simuler les réponses du Professeur avec le kit audio de secours (téléphone + playlist MP3).
|
||||
3. Utiliser le code de secours maître (imprimé sur la fiche lamifiée valise 1) pour ouvrir P7 manuellement.
|
||||
|
||||
---
|
||||
|
||||
## Mode Hors-Ligne (sans WiFi/internet)
|
||||
|
||||
Tous les puzzles fonctionnent **entièrement sans WiFi** via le mesh ESP-NOW local :
|
||||
|
||||
| Fonction | Online (WiFi dispo) | Offline (ESP-NOW seul) |
|
||||
|----------|---------------------|------------------------|
|
||||
| NPC voix (XTTS-v2) | Live depuis KXKM-AI | Fallback MP3 SD card |
|
||||
| NPC voix (Piper TTS) | Tower:8001 | Fallback MP3 SD card |
|
||||
| Détection NFC P6 | Locale (pas de réseau) | Identique |
|
||||
| QR scanner P3 | Locale (BOX-3 camera) | Identique |
|
||||
| Analytics | Cache local, upload post-jeu | Cache local |
|
||||
| Scoring + diplôme PDF | En mémoire BOX-3 | En mémoire BOX-3 |
|
||||
|
||||
> **Recommandation :** générer le pool MP3 XTTS avant chaque déploiement si de nouvelles phrases ont été ajoutées (`python3 tools/tts/generate_xtts_pool.py`).
|
||||
@@ -0,0 +1,220 @@
|
||||
# Suitcase Layout — Zacus V3 Mobile Kit
|
||||
|
||||
3 suitcases total. All foam cutouts use 10mm EVA foam sheets.
|
||||
Foam cavities are cut 1mm undersize for snug friction fit.
|
||||
|
||||
---
|
||||
|
||||
## Overview — 3-Suitcase System
|
||||
|
||||
| Suitcase | Name | Size | Contents | Weight est. |
|
||||
|----------|----------------|------------|-------------------------------------------|-------------|
|
||||
| A | Hub Suitcase | 57L rigid | BOX-3, router, powerbanks, RTC_PHONE, audio | ~6 kg |
|
||||
| B | Puzzle Objects | 42L soft | P1, P4, P5, P7 puzzle boxes + accessories | ~4 kg |
|
||||
| C | Props & Cards | 42L soft | P2 board, P3 cards, P6 tablet, cables, signage | ~3 kg |
|
||||
|
||||
---
|
||||
|
||||
## Suitcase A — Hub Suitcase (57L Rigid ABS)
|
||||
|
||||
**Dimensions:** 750 × 500 × 280 mm interior usable space
|
||||
**Foam layout:** 2 layers — top tray + base
|
||||
|
||||
```
|
||||
SUITCASE A — TOP VIEW (750mm wide × 500mm deep)
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ┌──────────────────┐ ┌────────────────────┐ ┌─────────────┐ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ ESP32-S3-BOX-3 │ │ GL.iNet GL-MT3000 │ │ RTC_PHONE │ │
|
||||
│ │ 200×120×70mm │ │ 150×100×45mm │ │ 120×80×40 │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ └──────────────────┘ └────────────────────┘ └─────────────┘ │
|
||||
│ │
|
||||
│ ┌────────────────────────┐ ┌──────────────────────────────────┐ │
|
||||
│ │ Anker Powerbank #1 │ │ Anker Powerbank #2 │ │
|
||||
│ │ 175×80×28mm │ │ 175×80×28mm │ │
|
||||
│ └────────────────────────┘ └──────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────┐ ┌────────────────────────────────────────┐ │
|
||||
│ │ Anker PB #3 │ │ JBL Go 3 Speaker 85×85×45mm │ │
|
||||
│ │ 175×80×28mm │ └────────────────────────────────────────┘ │
|
||||
│ └──────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────────┐ │
|
||||
│ │ USB-C Cables roll (10× 1m) — 200×100×60mm bundle │ │
|
||||
│ └──────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ LED Strip USB 3m (rolled) 60×60×30mm │ │
|
||||
│ │ USB Hub 4-port 90×35×20mm (cable mgmt) │ │
|
||||
│ │ Ethernet patch cable 1m (zip bag) │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
LID (zip pocket):
|
||||
- GL.iNet power adapter
|
||||
- Quick-start card (laminated A5)
|
||||
- Master game guide booklet
|
||||
- Spare micro-USB cable (BOX-3 fallback)
|
||||
```
|
||||
|
||||
### Foam Cutout Specifications (Suitcase A)
|
||||
|
||||
| Item | Foam Cavity (L×W×D) | Layer |
|
||||
|--------------------|---------------------|---------|
|
||||
| BOX-3 | 202×122×72 mm | Base |
|
||||
| GL-MT3000 | 152×102×47 mm | Base |
|
||||
| RTC_PHONE | 122×82×42 mm | Base |
|
||||
| Powerbank × 3 | 177×82×30 mm each | Base |
|
||||
| JBL Go 3 | 87×87×47 mm | Base |
|
||||
| Cable bundle | 202×102×62 mm | Base |
|
||||
| LED strip roll | 62×62×32 mm | Base |
|
||||
| USB Hub | 92×37×22 mm | Base |
|
||||
| Ethernet cable | 50×50×15 mm | Base |
|
||||
|
||||
---
|
||||
|
||||
## Suitcase B — Puzzle Objects (42L Softcase)
|
||||
|
||||
**Dimensions:** 600 × 420 × 240 mm interior usable space
|
||||
**Foam layout:** 1 layer with tall cutouts; lid pocket for accessories
|
||||
|
||||
```
|
||||
SUITCASE B — TOP VIEW (600mm wide × 420mm deep)
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ┌───────────────────┐ ┌──────────────────────────┐ │
|
||||
│ │ │ │ │ │
|
||||
│ │ P1 Boîte Sonore │ │ P4 Poste Radio Rétro │ │
|
||||
│ │ 150×100×80mm │ │ 200×150×120mm │ │
|
||||
│ │ │ │ │ │
|
||||
│ └───────────────────┘ └──────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌───────────────────┐ ┌──────────────────────────┐ │
|
||||
│ │ │ │ │ │
|
||||
│ │ P5 Télégraphe │ │ P7 Coffre Final │ │
|
||||
│ │ 180×80×60mm │ │ 250×200×150mm │ │
|
||||
│ │ │ │ │ │
|
||||
│ └───────────────────┘ └──────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────┐ │
|
||||
│ │ Accessory zone │ │
|
||||
│ │ - Morse reference cards (zip bag) 150×120×5mm │ │
|
||||
│ │ - Arcade button spare caps (bag) 80×80×30mm │ │
|
||||
│ │ - USB-C cables 4× short (bag) 100×50×20mm │ │
|
||||
│ └──────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
|
||||
LID pocket:
|
||||
- Puzzle reset cards (laminated, one per puzzle)
|
||||
- Allen key M3 + M2.5 set
|
||||
- Spare WS2812B LEDs (bag)
|
||||
- Spare fuse kit
|
||||
```
|
||||
|
||||
### Foam Cutout Specifications (Suitcase B)
|
||||
|
||||
| Item | Foam Cavity (L×W×D) | Notes |
|
||||
|--------------------|---------------------|--------------------------------|
|
||||
| P1 Boîte Sonore | 152×102×82 mm | Snug — PETG box |
|
||||
| P4 Poste Radio | 202×152×122 mm | Extra 2mm clearance for knob |
|
||||
| P5 Télégraphe | 182×82×62 mm | Brass key faces up |
|
||||
| P7 Coffre Final | 252×202×152 mm | Heaviest piece — bottom layer |
|
||||
| Accessory strip | 300×120×35 mm | Shared zone, non-foam dividers |
|
||||
|
||||
**Weight check:**
|
||||
- P7 coffre: ~2.5kg (wood + electronics)
|
||||
- P4 radio: ~0.8kg (PLA + 18650s)
|
||||
- P1 boîte: ~0.5kg (PETG + speaker)
|
||||
- P5 télégraphe: ~0.4kg (PETG + brass key)
|
||||
- Accessories: ~0.3kg
|
||||
- Foam: ~0.4kg
|
||||
- **Total suitcase B: ~4.9kg** — within 5kg carry-on limit
|
||||
|
||||
---
|
||||
|
||||
## Suitcase C — Props & Cards (42L Softcase)
|
||||
|
||||
**Dimensions:** 600 × 420 × 240 mm interior usable space
|
||||
|
||||
```
|
||||
SUITCASE C — TOP VIEW (600mm wide × 420mm deep)
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ P2 Magnetic Folding Board │ │
|
||||
│ │ 600×400mm folded to 300×400×20mm │ │
|
||||
│ │ (occupies full width when folded) │ │
|
||||
│ │ │ │
|
||||
│ └──────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────┐ ┌──────────────────────────┐ │
|
||||
│ │ │ │ │ │
|
||||
│ │ P6 Wooden │ │ P3 QR Cards + Signage │ │
|
||||
│ │ Tablet │ │ - 6× A5 QR cards │ │
|
||||
│ │ 300×200×20mm │ │ - 10× A4 labels │ │
|
||||
│ │ (slots up) │ │ - 1× A3 puzzle map │ │
|
||||
│ │ │ │ All in clear folder │ │
|
||||
│ └──────────────────┘ └──────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────┐ │
|
||||
│ │ P6 Symbol Pieces bag (12 pcs, 40×40×8mm each) │ │
|
||||
│ │ Total bag: 200×150×30mm │ │
|
||||
│ └──────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────┐ │
|
||||
│ │ P2 Component Kit │ │
|
||||
│ │ - Magnetic resistors, LEDs, wires (bag) │ │
|
||||
│ │ - Foam-padded compartment tray 200×150×40mm │ │
|
||||
│ └──────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────┐ │
|
||||
│ │ Misc │ │
|
||||
│ │ - Setup guide A4 folder │ │
|
||||
│ │ - Diploma templates (10 sheets, laminated) │ │
|
||||
│ │ - USB A-C adapter set (bag) │ │
|
||||
│ └──────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Foam Cutout Specifications (Suitcase C)
|
||||
|
||||
| Item | Foam Cavity (L×W×D) | Notes |
|
||||
|-------------------------|-----------------------|-------------------------------|
|
||||
| P2 Folding Board | 302×402×22 mm | Full width — 2 foam strips hold|
|
||||
| P6 Wooden Tablet | 302×202×22 mm | Slots face up — NFC tags safe |
|
||||
| P6 Symbol Pieces bag | 202×152×32 mm | Soft bag, low cavity depth |
|
||||
| P3 Cards folder | 220×160×20 mm | Clear A4 folder, thin section |
|
||||
| P2 Component tray | 202×152×42 mm | Padded compartment tray |
|
||||
| Misc folder zone | 220×160×20 mm | Documents + diplomas |
|
||||
|
||||
---
|
||||
|
||||
## Total Kit Weight & Volume
|
||||
|
||||
| Suitcase | Weight | Volume | Check-in |
|
||||
|----------|--------|--------|----------|
|
||||
| A (Hub) | ~6.0 kg | 57L | Yes — checked |
|
||||
| B (Puzzles) | ~4.9 kg | 42L | Cabin OK |
|
||||
| C (Props) | ~3.0 kg | 42L | Cabin OK |
|
||||
| **Total** | **~13.9 kg** | **141L** | |
|
||||
|
||||
Kit fits 2-person team transport (car/train/plane). Suitcase A checked, B+C cabin luggage.
|
||||
|
||||
---
|
||||
|
||||
## Setup Procedure (Field Deployment)
|
||||
|
||||
1. Open Suitcase A: deploy BOX-3, plug router, connect all powerbanks via USB-C hub
|
||||
2. Open Suitcase B: place P1/P4/P5 at puzzle stations; P7 at final position
|
||||
3. Open Suitcase C: unfold P2 board at station; place P6 tablet; hide P3 QR cards per map
|
||||
4. Power-on sequence: Router → BOX-3 → P1..P7 (each has auto-pair on boot via ESP-NOW)
|
||||
5. BOX-3 dashboard shows all nodes: green = online, orange = pairing, red = fault
|
||||
6. Estimated setup time: 15 minutes for 2 staff members
|
||||
|
||||
See `docs/DEPLOYMENT_RUNBOOK.md` for full field procedure including audio check and NPC test.
|
||||
@@ -0,0 +1,500 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
collect_metrics.py — Collecte et analyse des métriques de playtest.
|
||||
|
||||
Lit les logs série ESP-NOW de BOX-3, parse les événements de résolution,
|
||||
demandes d'indices et décisions NPC. Génère un rapport Markdown de session
|
||||
et l'ajoute au fichier metrics_template.csv.
|
||||
|
||||
Usage:
|
||||
# Lire depuis port série (BOX-3 connecté en USB)
|
||||
python3 tools/playtest/collect_metrics.py --port /dev/cu.usbserial-XXXX
|
||||
|
||||
# Lire depuis un fichier log pré-capturé
|
||||
python3 tools/playtest/collect_metrics.py --log-file session_001.log
|
||||
|
||||
# Spécifier un identifiant de session
|
||||
python3 tools/playtest/collect_metrics.py --log-file session_001.log --session beta_001
|
||||
|
||||
# Écrire le rapport dans un dossier spécifique
|
||||
python3 tools/playtest/collect_metrics.py --log-file session_001.log --output-dir docs/playtest/reports/
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Constantes
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
PUZZLES = ["P1_SON", "P2_CIRCUIT", "P3_QR", "P4_RADIO", "P5_MORSE", "P6_SYMBOLES", "P7_COFFRE"]
|
||||
|
||||
PUZZLE_LABELS = {
|
||||
"P1_SON": "Séquence Sonore",
|
||||
"P2_CIRCUIT": "Circuit LED",
|
||||
"P3_QR": "QR Treasure",
|
||||
"P4_RADIO": "Fréquence Radio",
|
||||
"P5_MORSE": "Code Morse",
|
||||
"P6_SYMBOLES": "Symboles Alchimiques",
|
||||
"P7_COFFRE": "Coffre Final",
|
||||
}
|
||||
|
||||
EXPECTED_DURATIONS_S = {
|
||||
"P1_SON": 300,
|
||||
"P2_CIRCUIT": 360,
|
||||
"P3_QR": 240,
|
||||
"P4_RADIO": 300,
|
||||
"P5_MORSE": 420,
|
||||
"P6_SYMBOLES": 360,
|
||||
"P7_COFFRE": 300,
|
||||
}
|
||||
|
||||
METRICS_CSV = Path(__file__).parent.parent.parent / "docs" / "playtest" / "metrics_template.csv"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Patterns de log ESP-NOW / BOX-3
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Exemple : [12:05:33] [ESPNOW] P1_SON: SOLVED in 287s (attempts: 2)
|
||||
RE_SOLVED = re.compile(
|
||||
r"\[(?P<ts>\d{2}:\d{2}:\d{2})\]\s+\[ESPNOW\]\s+(?P<puzzle>P\d+_\w+):\s+SOLVED\s+in\s+(?P<duration>\d+)s"
|
||||
r"(?:\s+\(attempts:\s*(?P<attempts>\d+)\))?"
|
||||
)
|
||||
|
||||
# Exemple : [12:08:01] [ESPNOW] P1_SON: SKIPPED
|
||||
RE_SKIPPED = re.compile(
|
||||
r"\[(?P<ts>\d{2}:\d{2}:\d{2})\]\s+\[ESPNOW\]\s+(?P<puzzle>P\d+_\w+):\s+SKIPPED"
|
||||
)
|
||||
|
||||
# Exemple : [12:06:12] [NPC] HINT requested: P1_SON level 2
|
||||
RE_HINT = re.compile(
|
||||
r"\[(?P<ts>\d{2}:\d{2}:\d{2})\]\s+\[NPC\]\s+HINT requested:\s+(?P<puzzle>P\d+_\w+)\s+level\s+(?P<level>\d+)"
|
||||
)
|
||||
|
||||
# Exemple : [11:55:00] [GAME] START target=60min group_size=5
|
||||
RE_GAME_START = re.compile(
|
||||
r"\[(?P<ts>\d{2}:\d{2}:\d{2})\]\s+\[GAME\]\s+START\s+target=(?P<target>\d+)min\s+group_size=(?P<size>\d+)"
|
||||
)
|
||||
|
||||
# Exemple : [12:55:12] [GAME] END score=1150 profile=TECH profile_correct=true
|
||||
RE_GAME_END = re.compile(
|
||||
r"\[(?P<ts>\d{2}:\d{2}:\d{2})\]\s+\[GAME\]\s+END\s+score=(?P<score>\d+)"
|
||||
r"\s+profile=(?P<profile>\w+)\s+profile_correct=(?P<correct>\w+)"
|
||||
)
|
||||
|
||||
# Exemple : [11:56:10] [NPC] PROFILE_DETECTED: TECH
|
||||
RE_PROFILE = re.compile(
|
||||
r"\[(?P<ts>\d{2}:\d{2}:\d{2})\]\s+\[NPC\]\s+PROFILE_DETECTED:\s+(?P<profile>\w+)"
|
||||
)
|
||||
|
||||
# Exemple : [11:57:00] [NPC] PHRASE: adaptation.group_tech_detected.0 via XTTS
|
||||
RE_PHRASE = re.compile(
|
||||
r"\[(?P<ts>\d{2}:\d{2}:\d{2})\]\s+\[NPC\]\s+PHRASE:\s+(?P<key>\S+)\s+via\s+(?P<backend>\w+)"
|
||||
)
|
||||
|
||||
# Exemple : [12:10:00] [HW] FAILURE: P4_RADIO ESP-NOW timeout
|
||||
RE_HW_FAILURE = re.compile(
|
||||
r"\[(?P<ts>\d{2}:\d{2}:\d{2})\]\s+\[HW\]\s+FAILURE:\s+(?P<description>.+)"
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Parseur de logs
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def parse_log(lines: list[str]) -> dict:
|
||||
"""Parse les lignes de log BOX-3 et retourne les données de session."""
|
||||
session: dict = {
|
||||
"start_ts": None,
|
||||
"end_ts": None,
|
||||
"target_minutes": 0,
|
||||
"group_size": 0,
|
||||
"profile_detected": "UNKNOWN",
|
||||
"profile_correct": None,
|
||||
"final_score": 0,
|
||||
"puzzles": {},
|
||||
"hints": [],
|
||||
"phrases": [],
|
||||
"hw_failures": [],
|
||||
}
|
||||
|
||||
for line in lines:
|
||||
line = line.rstrip()
|
||||
|
||||
m = RE_GAME_START.search(line)
|
||||
if m:
|
||||
session["start_ts"] = m.group("ts")
|
||||
session["target_minutes"] = int(m.group("target"))
|
||||
session["group_size"] = int(m.group("size"))
|
||||
continue
|
||||
|
||||
m = RE_GAME_END.search(line)
|
||||
if m:
|
||||
session["end_ts"] = m.group("ts")
|
||||
session["final_score"] = int(m.group("score"))
|
||||
session["profile_detected"] = m.group("profile")
|
||||
session["profile_correct"] = m.group("correct").lower() == "true"
|
||||
continue
|
||||
|
||||
m = RE_PROFILE.search(line)
|
||||
if m:
|
||||
session["profile_detected"] = m.group("profile")
|
||||
continue
|
||||
|
||||
m = RE_SOLVED.search(line)
|
||||
if m:
|
||||
puzzle = m.group("puzzle")
|
||||
session["puzzles"][puzzle] = {
|
||||
"status": "SOLVED",
|
||||
"ts": m.group("ts"),
|
||||
"duration_s": int(m.group("duration")),
|
||||
"attempts": int(m.group("attempts")) if m.group("attempts") else 1,
|
||||
}
|
||||
continue
|
||||
|
||||
m = RE_SKIPPED.search(line)
|
||||
if m:
|
||||
puzzle = m.group("puzzle")
|
||||
session["puzzles"][puzzle] = {
|
||||
"status": "SKIPPED",
|
||||
"ts": m.group("ts"),
|
||||
"duration_s": None,
|
||||
"attempts": 0,
|
||||
}
|
||||
continue
|
||||
|
||||
m = RE_HINT.search(line)
|
||||
if m:
|
||||
session["hints"].append({
|
||||
"ts": m.group("ts"),
|
||||
"puzzle": m.group("puzzle"),
|
||||
"level": int(m.group("level")),
|
||||
})
|
||||
continue
|
||||
|
||||
m = RE_PHRASE.search(line)
|
||||
if m:
|
||||
session["phrases"].append({
|
||||
"ts": m.group("ts"),
|
||||
"key": m.group("key"),
|
||||
"backend": m.group("backend"),
|
||||
})
|
||||
continue
|
||||
|
||||
m = RE_HW_FAILURE.search(line)
|
||||
if m:
|
||||
session["hw_failures"].append({
|
||||
"ts": m.group("ts"),
|
||||
"description": m.group("description"),
|
||||
})
|
||||
|
||||
return session
|
||||
|
||||
|
||||
def _ts_to_minutes(ts_start: str | None, ts_end: str | None) -> float:
|
||||
"""Calcule la durée en minutes entre deux timestamps HH:MM:SS."""
|
||||
if not ts_start or not ts_end:
|
||||
return 0.0
|
||||
fmt = "%H:%M:%S"
|
||||
try:
|
||||
t0 = datetime.strptime(ts_start, fmt)
|
||||
t1 = datetime.strptime(ts_end, fmt)
|
||||
delta = t1 - t0
|
||||
if delta.total_seconds() < 0:
|
||||
# Passage minuit
|
||||
delta += timedelta(hours=24)
|
||||
return delta.total_seconds() / 60
|
||||
except ValueError:
|
||||
return 0.0
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Génération du rapport Markdown
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def generate_report(session_id: str, session: dict) -> str:
|
||||
"""Génère un rapport Markdown complet pour la session."""
|
||||
actual_minutes = _ts_to_minutes(session["start_ts"], session["end_ts"])
|
||||
target_minutes = session["target_minutes"]
|
||||
duration_accuracy_pct = (
|
||||
abs(actual_minutes - target_minutes) / target_minutes * 100
|
||||
if target_minutes > 0 else 0.0
|
||||
)
|
||||
|
||||
solved_puzzles = [p for p, d in session["puzzles"].items() if d["status"] == "SOLVED"]
|
||||
skipped_puzzles = [p for p, d in session["puzzles"].items() if d["status"] == "SKIPPED"]
|
||||
hints_total = len(session["hints"])
|
||||
hw_failures = session["hw_failures"]
|
||||
|
||||
# Backend TTS stats
|
||||
backends_used = [p["backend"] for p in session["phrases"]]
|
||||
xtts_count = backends_used.count("XTTS")
|
||||
piper_count = backends_used.count("PIPER")
|
||||
sdcard_count = backends_used.count("SDCARD")
|
||||
|
||||
lines = [
|
||||
f"# Rapport de Session — {session_id}",
|
||||
f"",
|
||||
f"Généré le : {datetime.now().strftime('%Y-%m-%d %H:%M')}",
|
||||
f"",
|
||||
f"---",
|
||||
f"",
|
||||
f"## Résumé",
|
||||
f"",
|
||||
f"| Métrique | Valeur |",
|
||||
f"|----------|--------|",
|
||||
f"| Session ID | `{session_id}` |",
|
||||
f"| Taille du groupe | {session['group_size']} joueurs |",
|
||||
f"| Durée cible | {target_minutes} min |",
|
||||
f"| Durée réelle | {actual_minutes:.1f} min |",
|
||||
f"| Précision durée | ±{duration_accuracy_pct:.1f}% |",
|
||||
f"| Profil NPC détecté | {session['profile_detected']} |",
|
||||
f"| Profil correct | {'OUI' if session['profile_correct'] else 'NON' if session['profile_correct'] is not None else 'N/A'} |",
|
||||
f"| Puzzles résolus | {len(solved_puzzles)}/{len(PUZZLES)} |",
|
||||
f"| Puzzles sautés | {len(skipped_puzzles)} |",
|
||||
f"| Indices utilisés | {hints_total} |",
|
||||
f"| Score final | {session['final_score']} |",
|
||||
f"| Pannes matériel | {len(hw_failures)} |",
|
||||
f"",
|
||||
]
|
||||
|
||||
# Critères de succès
|
||||
lines += [
|
||||
f"## Critères de Succès",
|
||||
f"",
|
||||
f"| Critère | Objectif | Résultat | Statut |",
|
||||
f"|---------|----------|----------|--------|",
|
||||
f"| Temps de setup | ≤ 15 min | — | — |",
|
||||
f"| Précision durée | ≤ ±10% | ±{duration_accuracy_pct:.1f}% | {'PASS' if duration_accuracy_pct <= 10 else 'FAIL'} |",
|
||||
f"| Profil NPC correct | OUI | {'OUI' if session['profile_correct'] else 'NON'} | {'PASS' if session['profile_correct'] else 'FAIL'} |",
|
||||
f"| Pannes matériel | 0 | {len(hw_failures)} | {'PASS' if not hw_failures else 'FAIL'} |",
|
||||
f"| Jeu terminé | OUI | {'OUI' if 'P7_COFFRE' in solved_puzzles else 'NON'} | {'PASS' if 'P7_COFFRE' in solved_puzzles else 'FAIL'} |",
|
||||
f"",
|
||||
]
|
||||
|
||||
# Détail puzzles
|
||||
lines += [
|
||||
f"## Détail par Puzzle",
|
||||
f"",
|
||||
f"| Puzzle | Statut | Durée réelle | Durée attendue | Écart | Tentatives | Indices |",
|
||||
f"|--------|--------|-------------|----------------|-------|-----------|---------|",
|
||||
]
|
||||
for puzzle_id in PUZZLES:
|
||||
label = PUZZLE_LABELS.get(puzzle_id, puzzle_id)
|
||||
data = session["puzzles"].get(puzzle_id, {"status": "NOT_REACHED", "duration_s": None, "attempts": 0})
|
||||
expected = EXPECTED_DURATIONS_S.get(puzzle_id, 0)
|
||||
puzzle_hints = [h for h in session["hints"] if h["puzzle"] == puzzle_id]
|
||||
|
||||
status = data["status"]
|
||||
duration_s = data.get("duration_s")
|
||||
duration_str = f"{duration_s}s" if duration_s is not None else "—"
|
||||
expected_str = f"{expected}s"
|
||||
ecart_str = f"{duration_s - expected:+d}s" if duration_s is not None else "—"
|
||||
attempts = data.get("attempts", 0)
|
||||
|
||||
lines.append(
|
||||
f"| {label} | {status} | {duration_str} | {expected_str} | {ecart_str} | {attempts} | {len(puzzle_hints)} |"
|
||||
)
|
||||
|
||||
lines += [""]
|
||||
|
||||
# Indices demandés
|
||||
if session["hints"]:
|
||||
lines += [
|
||||
f"## Indices Demandés",
|
||||
f"",
|
||||
f"| Heure | Puzzle | Niveau |",
|
||||
f"|-------|--------|--------|",
|
||||
]
|
||||
for hint in session["hints"]:
|
||||
puzzle_label = PUZZLE_LABELS.get(hint["puzzle"], hint["puzzle"])
|
||||
lines.append(f"| {hint['ts']} | {puzzle_label} | {hint['level']} |")
|
||||
lines += [""]
|
||||
|
||||
# NPC / voix
|
||||
if session["phrases"]:
|
||||
lines += [
|
||||
f"## Interventions NPC",
|
||||
f"",
|
||||
f"- Phrases jouées : {len(session['phrases'])}",
|
||||
f"- XTTS-v2 (GPU) : {xtts_count}",
|
||||
f"- Piper TTS : {piper_count}",
|
||||
f"- SD Card fallback : {sdcard_count}",
|
||||
f"",
|
||||
]
|
||||
|
||||
# Pannes matériel
|
||||
if hw_failures:
|
||||
lines += [
|
||||
f"## Pannes Matériel",
|
||||
f"",
|
||||
f"| Heure | Description |",
|
||||
f"|-------|-------------|",
|
||||
]
|
||||
for failure in hw_failures:
|
||||
lines.append(f"| {failure['ts']} | {failure['description']} |")
|
||||
lines += [""]
|
||||
|
||||
lines += [
|
||||
f"---",
|
||||
f"",
|
||||
f"*Rapport généré automatiquement par `tools/playtest/collect_metrics.py`*",
|
||||
]
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Écriture CSV
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def append_to_csv(session_id: str, session: dict, csv_path: Path, nps_score: float = 0.0) -> None:
|
||||
"""Ajoute une ligne de métriques au fichier CSV de référence."""
|
||||
actual_minutes = _ts_to_minutes(session["start_ts"], session["end_ts"])
|
||||
solved = [p for p, d in session["puzzles"].items() if d["status"] == "SOLVED"]
|
||||
skipped = [p for p, d in session["puzzles"].items() if d["status"] == "SKIPPED"]
|
||||
|
||||
row = {
|
||||
"session_id": session_id,
|
||||
"date": datetime.now().strftime("%Y-%m-%d"),
|
||||
"group_size": session["group_size"],
|
||||
"target_duration": session["target_minutes"],
|
||||
"actual_duration": round(actual_minutes, 1),
|
||||
"group_profile_detected": session["profile_detected"],
|
||||
"group_profile_actual": "",
|
||||
"puzzles_solved": len(solved),
|
||||
"puzzles_skipped": len(skipped),
|
||||
"hints_total": len(session["hints"]),
|
||||
"nps_score": nps_score,
|
||||
"notes": "",
|
||||
}
|
||||
|
||||
fieldnames = [
|
||||
"session_id", "date", "group_size", "target_duration", "actual_duration",
|
||||
"group_profile_detected", "group_profile_actual", "puzzles_solved",
|
||||
"puzzles_skipped", "hints_total", "nps_score", "notes",
|
||||
]
|
||||
|
||||
# Lire les lignes existantes
|
||||
existing_rows: list[dict] = []
|
||||
if csv_path.exists():
|
||||
with open(csv_path, newline="", encoding="utf-8") as f:
|
||||
reader = csv.DictReader(f)
|
||||
for r in reader:
|
||||
if r.get("session_id") != session_id:
|
||||
existing_rows.append(r)
|
||||
|
||||
# Ré-écrire avec la nouvelle ligne
|
||||
with open(csv_path, "w", newline="", encoding="utf-8") as f:
|
||||
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
||||
writer.writeheader()
|
||||
for r in existing_rows:
|
||||
writer.writerow(r)
|
||||
writer.writerow(row)
|
||||
|
||||
print(f"CSV mis à jour : {csv_path}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Lecture des sources de données
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def read_from_file(path: str) -> list[str]:
|
||||
"""Lit les lignes depuis un fichier log."""
|
||||
with open(path, encoding="utf-8") as f:
|
||||
return f.readlines()
|
||||
|
||||
|
||||
def read_from_serial(port: str, timeout_seconds: int = 7200) -> list[str]:
|
||||
"""Lit les lignes depuis un port série (BOX-3 connecté en USB)."""
|
||||
try:
|
||||
import serial # type: ignore
|
||||
except ImportError:
|
||||
print("Erreur : pyserial non installé. Lancer : pip install pyserial", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
lines: list[str] = []
|
||||
print(f"Lecture du port série {port} (Ctrl+C pour arrêter)...")
|
||||
with serial.Serial(port, baudrate=115200, timeout=1) as ser:
|
||||
start = datetime.now()
|
||||
game_ended = False
|
||||
while True:
|
||||
line = ser.readline().decode("utf-8", errors="replace")
|
||||
if line:
|
||||
lines.append(line)
|
||||
print(line, end="", flush=True)
|
||||
if "[GAME] END" in line:
|
||||
game_ended = True
|
||||
print("\n[collect_metrics] Fin de session détectée.")
|
||||
break
|
||||
elapsed = (datetime.now() - start).total_seconds()
|
||||
if elapsed > timeout_seconds:
|
||||
print(f"\n[collect_metrics] Timeout {timeout_seconds}s atteint.")
|
||||
break
|
||||
return lines
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Point d'entrée
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Collecte et analyse des métriques de playtest Zacus V3"
|
||||
)
|
||||
source = parser.add_mutually_exclusive_group(required=True)
|
||||
source.add_argument("--port", help="Port série BOX-3 (ex: /dev/cu.usbserial-XXXX)")
|
||||
source.add_argument("--log-file", help="Fichier log pré-capturé")
|
||||
|
||||
parser.add_argument("--session", default=None, help="Identifiant de session (ex: beta_001)")
|
||||
parser.add_argument("--output-dir", default=None, help="Dossier de sortie pour le rapport Markdown")
|
||||
parser.add_argument("--nps-score", type=float, default=0.0, help="Score NPS moyen du groupe (Q1, 0-10)")
|
||||
parser.add_argument("--no-csv", action="store_true", help="Ne pas mettre à jour le fichier CSV")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Identifiant de session
|
||||
session_id = args.session or f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
||||
|
||||
# Lecture des données
|
||||
if args.port:
|
||||
lines = read_from_serial(args.port)
|
||||
else:
|
||||
lines = read_from_file(args.log_file)
|
||||
|
||||
if not lines:
|
||||
print("Erreur : aucune donnée à analyser.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Analyse
|
||||
session = parse_log(lines)
|
||||
|
||||
# Rapport Markdown
|
||||
report_md = generate_report(session_id, session)
|
||||
|
||||
output_dir = Path(args.output_dir) if args.output_dir else Path(__file__).parent.parent.parent / "docs" / "playtest" / "reports"
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
report_path = output_dir / f"{session_id}_report.md"
|
||||
report_path.write_text(report_md, encoding="utf-8")
|
||||
print(f"Rapport Markdown : {report_path}")
|
||||
|
||||
# Affichage rapide
|
||||
actual_minutes = _ts_to_minutes(session["start_ts"], session["end_ts"])
|
||||
solved = [p for p, d in session["puzzles"].items() if d["status"] == "SOLVED"]
|
||||
print(f"\nRésumé session {session_id}:")
|
||||
print(f" Groupe : {session['group_size']} joueurs")
|
||||
print(f" Durée : {actual_minutes:.1f} min / {session['target_minutes']} min cible")
|
||||
print(f" Profil NPC : {session['profile_detected']} ({'correct' if session['profile_correct'] else 'incorrect'})")
|
||||
print(f" Puzzles : {len(solved)}/{len(PUZZLES)} résolus, {len(session['hints'])} indices")
|
||||
print(f" Score : {session['final_score']}")
|
||||
print(f" NPS : {args.nps_score}/10")
|
||||
print(f" HW pannes : {len(session['hw_failures'])}")
|
||||
|
||||
# Mise à jour CSV
|
||||
if not args.no_csv:
|
||||
append_to_csv(session_id, session, METRICS_CSV, nps_score=args.nps_score)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user