J’ai amélioré le code source de l’OctoAlert de mon fils pour lui ajouter des animations et un jeu de Simon.
Comme d’habitude le code est disponible sur github. Mais cette fois-ci je vais détailler certains points.
Sommaire
Le main
Le « main » du projet est relativement simple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include "OctoAlert.h" void setup() { // Init serial logging Serial.begin(9600); Serial.println("Start OctoAlert Device"); // Init shield and neopixels InputManager::getInstance(); OutputManager::getInstance(); } void loop() { // Update state manager InputManager::getInstance()->update(); OctoStateManager::getInstance()->update(); OutputManager::getInstance()->update(); } |
Donc à chaque cycle, on lis le entrées (les boutons), on demande à la « machine à état » de mettre à jour ses données, et on met à jour les sorties (les LEDs et le son). Tout simple !
Les états
Au niveau de l’architecture logicielle, j’ai implémenté un arbre de « machines à état ». Au démarrage, nous sommes dans l’état StandBy. Cet état n’a pas de durée particulière, il ne peut être interrompu que par l’appui d’un bouton. Ensuite il y a un état OctoAlert qui joue la musique et l’animation led du célèbre dessin animé, celui ci s’arrête lorsque le son se termine, on retourne alors à l’état parent. Il y a également un état OctoButton pour chaque personnage à chaque coin du boîtier. A chaque fois, une animation et un son sont joués. En outre, il y a un état Simon.
Ci-contre, la hiérarchie des états. c’est une hiérarchie d’implémentation et non une hiérarchie d’appel.
Les masques
Les masques que j’utilise un peu partout dans mon code sont une combinaison de masques type défini dans LedMapping.h. Ils font le lien entre des formes de base (le quart en haut à droite, le petit cercle) et les identifiants de chaque LED. Les identifiants figurent sur le graphique ci-contre.
Par exemple, voici quelques masque à utiliser et combiner :
57 58 59 60 61 62 63 64 65 66 67 68 |
#define LED_MASK_NONE 0b00000000000000000000000000000000000000000 #define LED_MASK_ALL 0b11111111111111111111111111111111111111111 #define LED_MASK_TOP_RIGHT_QUARTER 0b11111000000000000111111000000000000000000 #define LED_MASK_BOTTOM_RIGHT_QUARTER 0b10000111100000000000000111111000000000000 #define LED_MASK_BOTTOM_LEFT_QUARTER 0b10000000011110000000000000000111111000000 #define LED_MASK_TOP_LEFT_QUARTER 0b10000000000001111000000000000000000111111 #define LED_MASK_CENTER 0b10000000000000000000000000000000000000000 #define LED_MASK_LITTLE_RING 0b01111111111111111000000000000000000000000 #define LED_MASK_BIG_RING 0b00000000000000000111111111111111111111111 #define LED_MASK_CROSS 0b10010010000100100000100001000000100001000 #define LED_MASK_RADAR 0b01111111011111110111111111110111111111110 |
Ainsi, si je veux allumer le quart en haut à droite du petit cercle uniquement (c’est à dire les leds 29, 28, 27 et 26), il me suffit d’utiliser en paramètre une combinaison de 2 masques : LED_MASK_TOP_RIGHT_QUARTER et LED_MASK_LITTLE_RING.
Rotation de masques
Pour les animations, j’ai notamment un certain nombre de rotations de masques à gérer. Ceci est fait par la fonction suivante dans la classe RotaryMask :
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
void RotaryMask::rotate() { // Because we have 24 leds on the big ring and 16 on the little (24/16 = 3/2) rotateRing(m_waitingCountBigRing, m_baseWaitingTicks*2, LED_MASK_BIG_RING); rotateRing(m_waitingCountLittleRing, m_baseWaitingTicks*3, LED_MASK_LITTLE_RING); } void RotaryMask::rotateRing(uint8_t &waitingCounter, uint8_t waitingTicks, uint64_t rotationLocationMask) { waitingCounter--; if (!waitingCounter && (!m_maxRotationCount || m_currentRotationCount)) { waitingCounter = waitingTicks; rotateMask(m_currentMask, rotationLocationMask); if (m_maxRotationCount && m_currentRotationCount && m_currentMask==m_startingMask) { m_currentRotationCount--; } } } bool RotaryMask::isFinished() { return (m_maxRotationCount && !m_currentRotationCount); } void RotaryMask::rotateMask(uint64_t &colorMaskToRotate, uint64_t rotationLocationMask) { // Get the lowest set bit int rotationStartIndex = __builtin_ctzll(rotationLocationMask); uint64_t ring = colorMaskToRotate; // Remove useless bits ring &= rotationLocationMask; // Shift if necessary for rotation ring >>= rotationStartIndex; // Rotate by 1 ring = ((ring >> 1) | (ring << (__builtin_popcountll(rotationLocationMask) - 1))); // Shift to restore maskIndex ring <<= rotationStartIndex; // Clean any polluted bit ring &= rotationLocationMask; // Override rotated bits colorMaskToRotate = ((colorMaskToRotate & ~rotationLocationMask) | ring); } |
L’animation de victoire dans le jeu de Simon
Donc pour faire l’animation de victoire du jeu de Simon (4 quarts de 4 couleurs différentes qui tournent), voici le code utilisé :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
/* * SimonWinGameState.cpp * * Created on: 5 févr. 2016 * Author: nicolas */ #include "SimonWinGameState.h" SimonWinGameState::SimonWinGameState() : SoundState(SOUND_SIMON_WINGAME), m_redMask(new RotaryMask(LED_MASK_TOP_RIGHT_QUARTER & ~LED_MASK_CENTER, WAITING_TICKS)), m_blueMask(new RotaryMask(LED_MASK_BOTTOM_RIGHT_QUARTER & ~LED_MASK_CENTER, WAITING_TICKS)), m_yellowMask(new RotaryMask(LED_MASK_BOTTOM_LEFT_QUARTER & ~LED_MASK_CENTER, WAITING_TICKS)), m_greenMask(new RotaryMask(LED_MASK_TOP_LEFT_QUARTER & ~LED_MASK_CENTER, WAITING_TICKS)), m_red(COLOR_RED), m_blue(COLOR_BLUE), m_yellow(COLOR_YELLOW), m_green(COLOR_GREEN) {} void SimonWinGameState::activate() { SoundState::activate(); OutputManager::getInstance()->m_octoAlertLeds->off(LED_MASK_CENTER); } void SimonWinGameState::update() { SoundState::update(); OutputManager::getInstance()->m_octoAlertLeds->setColor(m_redMask->getValue(), m_red, false); OutputManager::getInstance()->m_octoAlertLeds->setColor(m_blueMask->getValue(), m_blue, false); OutputManager::getInstance()->m_octoAlertLeds->setColor(m_yellowMask->getValue(), m_yellow, false); OutputManager::getInstance()->m_octoAlertLeds->setColor(m_greenMask->getValue(), m_green, false); m_redMask->rotate(); m_blueMask->rotate(); m_yellowMask->rotate(); m_greenMask->rotate(); } |
Ping :Octo-Alerte : Un cadeau d'anniversaire unique - Bidouilles Factory
CC nico! j’ai regardé ta vidéo! pas sûre de savoir apprécier la performance en matière de « technologie » mais j’ai cru comprendre que tu t’amusais bien!! ;-)) j’espère que vous allez bien? des bisous!!
Salut Stéph ! Oui, je me suis bien amusé, mais c’était en janvier que je l’ai fait. Je n’ai pris le temps de faire une vidéo et un billet qu’hier. Tout va bien pour nous ! Donne moi des nouvelles en PV ! Bisous !
Bonjour,
Désolé, je n’utilise pas forcément le bon canal pour vous contacter, j’espère que vous ne m’en voudrez pas.
Je suis un particulier de Nantes et j’ai besoin de faire développer et réaliser pour mon usage privé, un appareil qui :
– réagit quand un bruit est émis à proximité (réaction à un seuil de niveau sonore réglable),
– déclenche le fonctionnement d’un autre appareil pendant une durée réglable (typiquement, une petite pompe qui aspire de l’eau dans un bassin et la projette à 50 cm de haut ou une roue à aubes qui se met à tourner ou l’émission d’un son aléatoire). Le détecteur et les appareils esclaves doivent être distincts et communiquer sans fils,
– fonctionne dans des plages horaires réglables,
– fonctionne en extérieur (étanchéité minimale aux intempéries).
– alimentation électrique par batteries.
Je suis prêt à assurer une part du support technique au développement et à en financer le coût.
Seriez-vous intéressé par ce genre de réalisation? Le cas échéant, connaitriez-vous un particulier ou une autre association qui seraient intéressés?
Merci pour votre retour.