Thibault m’a demandé il y a quelques mois des conseils pour fabriquer un jeu de quiz de type Questions pour un champion. L’objectif est de fournir un système de buzzers à des équipes, capable de détecter lequel a été actionné en premier :
Je me suis pris au jeu (…) à tel point que la tâche donner quelques conseils s’est petit à petit transformée en tout refaire de A à Z. Je vais maintenant décrire ce projet et ses évolutions, architecturé autour d’ESP8266, d’ESP32 puis de Raspberry Pi.
Genèse du projet
Ce type de jeu est plutôt classique, aussi, quelqu’un l’a certainement déjà fait avant nous. Il faut que les buzzers communiquent sans fil, ils vont donc probablement embarquer un ESP32, ESP8266 ou nRF24L01 : ce sont les circuits à la mode qui le permettent. Plutôt que de réinventer la roue, regardons donc ce qui existe déjà.
Une recherche avec ces mots-clés nous amène sur le projet Github suivant : https://github.com/bmellink/Quiz. L’auteur a choisi une architecture similaire à celle que j’imaginais : un hub central et des boutons qui s’y connectent. Le hub embarque en outre une interface Web qui affiche les scores et diffuse des sons lors de l’appui sur les buzzers. Relié à un vidéoprojecteur et à la sono, ce sera parfait !
Je possède déjà des boutons de 10 cm de diamètre parfaitement adaptés, qui sont d’ailleurs les mêmes que ceux de l’auteur du projet :
Tout est déjà sur le dépôt Git : le code du hub, des boutons et les schémas électriques. Le hub est très simple matériellement et se résume à un module ESP32 alimenté par son port USB : rien de particulier à fabriquer. Je propose à Thibault de réaliser un PCB pour les boutons : puisqu’il faut en construire plusieurs, cela vaut particulièrement le coup. Ce sera aussi plus propre et pratique.
Réalisation du PCB des boutons
Le schéma conçu par l’auteur est simple mais efficace : deux piles 1,5V en série pour l’alimentation, un ESP8266 avec un GPIO en entrée pour le bouton, un GPIO en sortie pour la LED qui l’illumine et une entrée analogique pour mesurer la tension des piles :
Je n’ai rien à redire à ce sujet, sauf la manière dont il pilote la LED : il mesure manuellement le gain de chacun des transistors et les utilise en mode générateur de courant. Ce n’est pas comme ça qu’il faut procéder (le gain d’un transistor est un paramètre bien trop variable pour s’appuyer dessus de la sorte). Je supprime donc le transistor et pilote la LED directement avec le GPIO et une résistance en série, ce sera suffisant. Veiller à choisir une LED avec une tension de seuil pas trop élevée (maxi 3,0V, tension des piles).
Je réalise donc sous Eagle le schéma et le routage de ce PCB minimaliste. On voit :
- en haut les deux piles AAA en série avec un interrupteur on/off à glissière,
- en-dessous l’ESP8266 (Wemos D1 mini),
- à droite les test pads sur lesquels viendra se souder le bouton :
Sur le routage, on trouve les deux supports de pile de chaque côté de la carte, une découpe pour venir encastrer le bouton au milieu et le module ESP8266 en-dessous :
J’ai ensuite commandé 10 exemplaires de ce PCB chez les Chinois pour le prix d’une pinte de bière. Dix jours plus tard, je reçois mon œuvre dans ma boîte aux lettres :
Quelques heures plus tard, les boutons sont assemblés :
On peut commencer à s’amuser…
Partie logicielle : détermination du joueur le plus rapide
J’ai terminé ma part du travail… Je laisse donc Thibault prendre le relais. La théorie voudrait qu’il suffise de flasher les codes fournis pour qu’ils fonctionnent. Évidemment, cela n’est pas le cas. Deux autres personnes ont été confrontées au même problème et ont ouvert un ticket : https://github.com/bmellink/Quiz/issues/1. Sans réponse de l’auteur…
Plutôt que de débugguer un code qui n’est pas le nôtre, je propose de complètement le réécrire. C’est radical, mais ce sera aussi l’occasion de simplifier drastiquement le programme. En effet, beaucoup de raisons que je ne listerai pas ici me font penser qu’il est très (trop) lourd, même pour un ESP32 plutôt costaud.
Je suis en vacances, tous ces boutons n’attendent que moi… C’est trop tentant, j’informe Thibault que je m’occupe également du code. Le bougre ne dit pas non !!
Avant de continuer, je dois expliquer rapidement le scénario : initialement, tous les boutons sont « en attente ». Ce sont les phases où la question est énoncée par le maître du jeu et lorsque les joueurs réfléchissent. Les buzzers clignotent rapidement. Dès qu’un joueur veut s’exprimer, il appuie sur son bouton. Le premier à le faire voit son buzzer s’allumer de manière fixe tandis que tous les autres s’éteignent. Il peut alors donner oralement sa réponse, qui est acceptée ou refusée par le maître du jeu. Ce dernier relance ensuite un cycle pour la question suivante.
Première tentative
Comme sur le code original, des serveurs HTTP tournent sur le hub et les boutons pour échanger. Je trouve cette solution lourde, mais elle est simple à mettre en place. Et puis, si c’est celle que l’auteur a choisi, elle doit certainement fonctionner ? (LOL vous me voyez venir).
En gros, lors de l’appui sur un bouton, il envoie une requête HTTP au hub qui lui répond un booléen indiquant s’il est le premier à avoir appuyé, ou pas. En fonction du retour, le buzzer reste alors allumé ou s’éteint.
Toutefois, il faut que tous les autres boutons s’éteignent dès qu’une personne a buzzé, même sans action de leur part. Deux possibilités donc :
- soit les boutons font régulièrement une requête au hub pour scruter si un autre joueur a répondu ou pas,
- soit le hub, dès qu’un premier joueur a répondu, envoie une requête successivement sur tous les autres pour leur demander de s’éteindre.
L’auteur du dépôt Git est parti sur la seconde solution, que j’adopte également, pas trop convaincu. D’après mes mesures, une requête HTTP prend entre 40 ms et 500 ms… quand elle fonctionne. En effet, l’ESP32 est capable de gérer qu’un seul client à la fois et si tous les boutons buzzent et contactent le hub quasi en même temps, les délais de réponse peuvent exploser voire le faire planter.
En pratique, ce n’est pas exploitable. Les délais de réponse sont bien trop aléatoires et le système est trop lent pour être utilisé dans de bonnes conditions. Les plantages ne sont pas rares non plus.
Deuxième tentative
Puisque le programme a été écrit par un softeux qui code de la même manière sur une config de gamer que sur un microcontrôleur, je reprends tout à ma sauce.
Je supprime les serveurs HTTP et les remplace par de simples paquets UDP. C’est beaucoup plus léger et surtout, il est possible d’émettre en broadcast. Il suffit alors que le hub envoie régulièrement un paquet contenant :
- soit l’information qu’une question est en cours
- soit l’identifiant du bouton vainqueur
pour que chaque bouton puisse se comporter de manière adéquate :
- clignotement lorsqu’une question est en cours
- allumé si l’ID du bouton vainqueur est le sien, éteint sinon
Il n’y a pas de machine d’état sur le bouton et il peut repartir comme si de rien n’était s’il est redémarré ou déconnecté en cours de jeu.
Lors de l’appui sur un bouton, il envoie un paquet au hub, cette fois en unicast, pour l’en informer :
Le résultat est clairement incomparable : le jeu est vraiment exploitable désormais, même s’il reste quelques petits plantages ou ralentissement occasionnels.
Fabrication matérielle du hub
Lorsque le joueur le plus rapide a buzzé, il a le droit de répondre (oralement) à la question. Sa réponse peut être soit acceptée, soit refusée par le maître du jeu. Il lui faut donc une interface pour cela (2 boutons) pour que le score soit incrémenté sur l’interface Web seulement en cas de réponse correcte.
Il faut également un routeur Wi-Fi : même si l’ESP32 du hub est capable de fonctionner en mode point d’accès (c’est ce que j’avais initialement prévu), la portée du réseau est très faible dans ce mode (5-10m seulement).
Je recycle donc un vieux routeur TP-Link en 802.11g, de nos jours inutilisable pour faire autre chose que de l’IoT :
Je le démonte pour gagner en encombrement et l’intègre dans un boîtier de récupération. Il aurait été fait sur mesure qu’il n’aurait pas été mieux adapté. J’installe aussi deux boutons connectés à l’ESP32, vert et rouge, pour accepter ou refuser la réponse.
J’en profite pour soustraire au régulateur 3V3 du routeur un peu de courant pour alimenter le microcontrôleur. Avec le routeur Wi-Fi, la portée du signal est d’une trentaine de mètres autour de lui.
Ainsi, le hub s’utilise de la manière suivante :
- Une fois connecté au secteur, le routeur et l’ESP32 démarrent.
- Initialement, le jeu est dans le mode « question en cours ». Le maître du jeu énonce la question et les joueurs peuvent répondre. Les boutons du hub sont inopérants pour l’instant et éteints.
- Lorsque le premier joueur appuie sur son buzzer, les 2 boutons du hub s’éclairent, requérant ainsi l’attention du maître du jeu pour accepter ou refuser la réponse.
- Ce dernier accepte (bouton vert) ou refuse (bouton rouge) la réponse et le cycle recommence.
Partie logicielle : interface Web et validation/refus de la réponse
L’interface Web est facultative et est destinée à être diffusée sur un vidéoprojecteur. Ses rôles sont multiples :
- Elle permet d’attribuer un nom à chaque bouton pour donner des noms rigolos aux équipes
- Elle liste les boutons connectés en affichant leur nom et leur score (= nombre de réponses acceptées par le maître du jeu)
- Lorsqu’un joueur buzze, des effets graphique et sonores sont générés pour apporter du dynamisme au jeu (désolé… je ne suis pas graphiste, j’ai fait de mon mieux 😂)
- Idem lorsque le maître du jeu accepte ou refuse la réponse
- Un mode Expert fait apparaître des fonctions supplémentaires : correction manuelle du score, renommage de l’équipe, choix du son émis lors du buzz, affichage du niveau batterie et de la qualité de la liaison radio…
Parfois utile, ces infos apportent de la lourdeur à l’interface, c’est pourquoi elles sont masquées en fonctionnement normal.
Pour réaliser cela, le hub met à disposition un fichier JSON contenant l’état de tous les boutons (score, nom, tension batterie, RSSI radio…) ainsi que des URLs pour renommer une équipe et corriger un score. Le nom des équipes et le bruitage associé est enregistré dans la mémoire Flash du hub.
L’interface Web recharge très régulièrement, en AJAX, ce fichier JSON et l’affiche sous forme de tableau.
Lorsqu’elle détecte l’incrémentation d’un score ou un autre événement, elle joue les effets graphiques et sonores.
Lorsqu’un bouton est déconnecté, il est affiché d’une manière plus « pâle » pour l’indiquer.
Amélioration
Le système est relativement exploitable même si parfois, l’interface Web met une seconde ou deux à détecter un événement et à lancer le son. Cela ne me plaît pas car la réactivité est primordiale pour avoir une bonne expérience.
Je décide donc de remplacer l’ESP32 du hub par quelque chose de plus costaud… Comme un Raspberry Pi. Un V1 B+ sorti d’un tiroir sera suffisant.
Je porte le code C++ de l’ESP32 en Python pour le RPi sans trop d’efforts puisque la programmation est orientée objet dans les 2 langages. Paradoxalement, garder du C++ sur le RPi m’aurait demandé plus de travail car beaucoup de bibliothèques utilisées sur l’ESP32 n’ont pas d’équivalent sur le RPi.
La carte SD est montée en lecture seule pour ne pas risquer de la corrompre, à l’exception d’une petite partition contenant les paramètres (noms et sons des buzzers).
Matériellement, l’adaptation est mineure. J’en profite pour remplacer le bloc secteur 12V par une alimentation USB 5V pour que le hub puisse être alimenté via une batterie externe (pratique pour que le maître du jeu n’ait pas de fil à la patte).
Résultat
Les performances et la fiabilité sont incomparables. Le système est enfin réactif et ne donne plus l’impression d’être sur le point de planter à tout moment.
Ci-dessous, une vidéo qui résume le projet :
C’est quasi émouvant de voir un veritable concepteur-developpeur prendre autant de temps ( peut être aussi de plaisir ? ) à exposer son projet par écrit.
Un seul mot : BRAVO (car nous avons déjà utilisé le merci)
Merci Frederic pour ton retour 😉 c’était effectivement un grand plaisir !