Publié par

Il y a 4 mois -

Temps de lecture 18 minutes

Un Mölkky connecté avec Node JS et du Bluetooth Low Energy

Jouons au Mölkky sans avoir à compter les points nous-mêmes

Si vous avez déjà joué au Mölkky, vous avez surement déjà prononcé ou entendu cette phrase ou un équivalent « On en est a combien ? ». Si vous ne savez pas ce qu’est le Mölkky : il s’agit d’un jeu de quilles finlandais dont le but est de marquer des points pour atteindre 50, en renversant les quilles avec un bâton lanceur. Généralement on est nombreux, c’est l’heure de l’apéritif et très rapidement personne ne sait où en est la partie.

Alors j’ai pensé : « Et si quelque chose comptait les points pour nous ? »

J’ai donc fait un prototype avec des beacons dotés d’un accéléromètre, et on l’a mis a l’épreuve en novembre dernier pendant la Xebicon 19.

On vous explique tout dans cet article.


 

/**/ 


Définir le proto

Les règles du jeu

Le principe du jeu est de faire tomber des quilles en bois à l’aide d’un bâton lanceur. Les quilles sont marquées de 1 à 12. La première équipe arrivant à totaliser exactement 50 points gagne la partie.

Au début du jeu, les quilles sont positionnées comme sur le schéma suivant :

 

 

Pour renverser les quilles, le joueur de la première équipe jette le bâton lanceur sur les quilles.

Trois situations sont alors possibles :

  • plusieurs quilles sont tombées : l’équipe gagne autant de points que de quilles renversées
  • une seule quille est tombée : l’équipe gagne le nombre de points inscrits sur la quille
  • aucune quille n’est tombée : le joueur réessaie dans la limite de 3 lancers ratés (cette règle diffère souvent)

On redresse ensuite les quilles à l’endroit où elles étaient tombées. Ainsi elles s’éparpillent au cours du jeu. C’est ensuite à l’équipe suivante de jouer.

Si une équipe dépasse 50 points, son score redescend à 25. Il faut en effet atteindre exactement un total de 50, pas plus.

Game play cible versus objectif du proto

Pour notre proto, on acceptera que pour arriver au résultat idéal, il nous faudra itérer. On part donc sur un scénario, qui pourrait se dérouler ainsi, avec des étapes supplémentaires au scénario idéal :

Le Hardware

Quelle thing et quelle techno pour notre solution IOT

Gyroscope, accéléromètre : de quels capteurs avons-nous besoin ?

La première chose qui vient à l’esprit pour connaitre la position d’une quille c’est « il nous faut un gyroscope dans la quille comme dans les smartphones ! ».

Après quelques recherches on comprend qu’en réalité un smartphone combine les données de plusieurs capteurs pour estimer sa position. Contrairement à ce qu’on imagine, notre smartphone n’a pas de gyroscope. Il a un gyromètre, un accéléromètre et un magnétomètre. Le smartphone les utilise pour obtenir la valeur absolue du tangage (mouvement de l’avant vers l’arrière) et du roulis (mouvement de droite à gauche). [source]

Alors, j’ai cherché un objet doté de ces trois capteurs. En gros j’ai recherché sur Google « gyroscope sensors ». J’ai exploré un bon nombre de pistes. J’ai regardé du côté d’Arduino et de Raspberry. Je suis aussi souvent tombée sur des capteurs qui semblaient parfaits comme celui-ci :

 

Mais voilà je voulais plus simple : hors de question de me mettre à faire des soudures pour brancher un circuit électronique à une batterie à l’intérieur d’une quille.

Des capteurs BLE et leurs contraintes

De fil en aiguille, j’ai découvert Espruino qui commençait à beaucoup me plaire. Ça parlait JavaScript et applications mobiles, pour la développeuse front que je suis c’était plutôt séduisant. Alors j’ai cherché des sensors Bluetooth. J’ai découvert des frameworks qui donnaient envie : Evothings et Johnny-Five par exemple.

Ma recherche s’est de plus en plus affinée et puis, je me suis dit que des sensors BLE (Bluetooth Low Energy) étaient effectivement la clé : il nous fallait des balises avec un accéléromètre ! Comme c’est basse consommation, une pile dans notre petit objet serait parfaite. Plusieurs solutions s’offraient à nous : Kontact.io, Blue Net Beacon, Sensoro et surtout Estimote.

Et puis en discutant avec les collègues, un jour l’un d’entre eux me dit « tu vas avoir une limite : les spécs de Bluetooth, c’est pas plus de 7 devices connectés simultanément à un device ! » 😱

Effectivement :

A master BR/EDR Bluetooth device can communicate with a maximum of seven devices in a piconet (an ad-hoc computer network using Bluetooth technology), though not all devices reach this maximum.

[source]

Qu’à cela ne tienne, je passerai par une passerelle !

Une gateway et 12 beacons : c’est Minew !

En faisant toutes ces recherches, je finissais toujours par tomber sur des beacons très peu documentés, mais aussi bien moins chers, vendus sur Alibaba par l’entreprise Minew. J’ai donc décidé de commander et d’essayer :

Les beacons transmettent les données de l’accéléromètre à la gateway par Bluetooth. Celle-ci communique ensuite ces données à l’adresse d’un serveur (cloud, local ou hébergé) en HTTP ou en MQTT. Elle a donc besoin d’être connectée par WiFi (ou par Ethernet).

Schéma fonctionnement beacons BLE vers gateway WIFI

Des beacons aux positions

Configurer les beacons

Pour allumer un beacon, il faut l’ouvrir et presser 3 secondes le bouton on. Il se met alors à clignoter quelques secondes puis à émettre des données. À partir de ce moment, il est actif et contactable.

Nous allons voir quelles sont ces données et comment les intercepter.

Pour le configurer, Minew a mis à disposition une application mobile téléchargeable (BeaconSET sur Google Play / BeaconSET sur l’Apple store). L’entreprise nous a également envoyé les sources de l’application native Android, qui peut servir de base pour un usage qui serait différent du nôtre.

Changer le mot de passe

Par défaut le mot de passe est minew123, il est vivement conseillé d’en choisir un nouveau.

BeaconSET - changement de mot de passe

Émettre les données de l’accéléromètre

Un beacon est conçu pour de multiples usages, notamment dans le marketing. Plusieurs « slots » sont alors configurables dans l’interface de configuration du beacon. Ici un slot en particulier nous intéresse c’est l’accéléromètre (ACC), que l’on souhaite actif en continu sans avoir besoin de le déclencher manuellement avec le bouton. Pour ce faire on positionne le trigger à motion (cf. la capture ci-dessous). On supprime donc les autres, à l’exception de INFO, qui est utile au beacon en lui-même et on le configure.

BeaconSET - réglages

 

La home de l’application nous permet alors de vérifier que le beacon transmet les valeurs de mouvement du beacon selon 3 axes orthogonaux : X (abscisse), Y (ordonnée) et surtout Z (cote).

BeaconSET - Beacons liste

 

On effectue ces opérations sur chacun des 11 beacons restants :

BeaconSET - all beacons

Configurer la gateway

Minew G1 Bluetooth to Wifi gateway router

Réseau local et wifi

Une fois allumée, la gateway diffuse un réseau WiFi « GW-XXXXXXXXXXXX », il n’y a pas de mot de passe par défaut. Lorsqu’on est connecté à ce WiFi, l’interface se trouve à l’adresse http://192.168.99.1

La première chose à faire est de changer et sécuriser le SSID que diffuse la gateway. Il faut aussi mettre un mot de passe sur l’interface.

Minew gateway interface - AP SSID configuration

Ensuite notre gateway a besoin d’être elle-même reliée à Internet pour pouvoir communiquer à des services cloud par exemple. Pour cela, on peut la connecter avec une prise Ethernet ou configurer un réseau Internet domestique dans l’onglet Network :

Minew gateway interface - newtwork settings

Après redémarrage, on sait si le routeur est bien connecté à Internet lorsque une adresse IP lui a été attribuée :

Minew gateway interface

Le fait d’avoir sélectionné le mode répétiteur sur la gateway, nous permet d’utiliser la connexion Internet à laquelle elle est elle-même connecté, lorsqu’on se connecte à son propre réseau WiFi.

Réception et émission des données

A priori, notre gateway reçoit les données des beacons allumés et doit pouvoir les transmettre à n’importe quel service. Comment vérifier ?

Dans un premier temps, utilisons la plateforme IOT uBeac, un service d’agrégation et de visualisation de données. Celle-ci nous permettra de vérifier que notre gateway transmet correctement ce qu’elle reçoit. Après avoir créé un compte, nous configurons une gateway et sélectionnons la nôtre : Minew.

Ubeac.io - reglage du gateway Minew

L’onglet HTTP nous donne le endpoint à copier et qui va nous servir à configurer le service sur l’interface de notre gateway :

Ubeac.io - Gateway endpoint url

 

Nous pouvons maintenant nous rendre sur l’interface de la gateway dans l’onglet Services et lui fournir le endpoint d’uBeac, sélectionnons également le format JSON :

Minew Gateway - service url

Si on bouge nos beacons, on peut alors voir apparaitre les requêtes en temps réel :

Ubeac.io - live request list

 

Et voir à quoi ressemble la data reçue pour un beacon :

Ubeac.io - Body data endpoint

Nous verrons plus tard comment interpréter ce JSON.

Le Software

Le materiel fonctionne. Il nous reste plus qu’à développer la partie software de notre Mölkky.

Créer notre propre endpoint pour recevoir les données

Mise en place d’un serveur Node

À la manière d’uBeac, nous créons un serveur qui, sur un endpoint en POST reçoit les données en continu pour les traiter. Pour cela nous utilisons le framework Express.

On créé une route /api/minew :

import express from 'express';

const router = express.Router();

router.post('/', async (req, res) => {
 const { body } = req;
 console.log({ body });
 res.end();
});

module.exports = router;

 

On peut déployer notre serveur sur un Raspberry ou chez un hébergeur. Notre endpoint sera donc http://192.168.99.103:8888/api/minew en local et nous pouvons le fournir à la gateway :

Gateway BLE - endpoint url service

 

C’est parti, notre serveur reçoit alors beaucoup d’informations, on note qu’il reçoit bien les informations de la gateway en elle-même mais aussi des informations de potentiels devices BLE autour :

Gateway WIFI - node server body

 

Améliorons la configuration de notre gateway pour filtrer et recevoir uniquement les informations de nos sensors. Désactivons l’envoie des données de la gateway et filtrons par adresse MAC. Ici l’exemple avec un seul beacon :

Gateway WIFI - Filter BLE devices

 

À présent notre serveur ne reçoit qu’un objet vide si on ne bouge pas les beacons en question et sinon il reçoit les données des beacons qu’on a indiqué à la gateway :

Node Sever - resultat des devices filtres

Interprétation des données reçues

Pour l’instant ce qu’on reçoit ne sont que des données brutes dans la clé rawData du body. Il s’agit d’un format de diffusion de paquet. Pour en savoir plus, je vous conseille l’article « Understanding the different types of BLE Beacons ».

ReelyActive nous fournit par exemple un outil en ligne pour déchiffrer ces données à partir de la valeur de rawData et en sélectionnant le bon sensor :

reelyActive advlib interface

Et voici, nous voyons enfin apparaitre les valeurs de nos accélérations :

reelyActive advlib result

Et en plus ReelyActive propose en open source une librairie qu’on va pouvoir utiliser : Advlib.

Transformons le log de notre route :

import advlib from 'advlib';
import express from 'express';
import isArray from 'lodash/isArray';

const router = express.Router();

router.post('/', async (req, res) => {
 const { body } = req;
 if( isArray(body) && body.length > 0 ) {
  body.forEach(({ rawData }) =>; {
   if( rawData ) {
    console.log(advlib.ble.data.process(rawData));
   }
  });
 }
 res.end();
});

module.exports = router;

 

Résultat :

POST /api/minew 200 0.650 ms - -
{ flags: [ 'LE General Discoverable Mode', 'BR/EDR Not Supported' ],
  complete16BitUUIDs: 'ffe1',
  serviceData:
   { uuid: 'ffe1',
     data: 'a10364000a000000f0a246a23f23ac',
     minew:
      { frameType: 'a1',
        productModel: 3,
        batteryPercent: 100,
        accelerationX: 0.0390625,
        accelerationY: 0,
        accelerationZ: 0.9375,
        macAddress: 'ac:23:3f:a2:46:a2' } } }

 

Nettoyons les données inutiles en filtrant ce qu’on reçoit de façon à ne garder que batteryPercent et surtout mac et accelerationZ. Ici SKITTLES est une constante importée qui associe chaque adresse MAC à un numéro de quilles :

router.post('/', async (req, res) => {
 const { body } = req;
 // Look for data concerning skittles sensors
 if (body && isArray(body) && body.length > 0) {
   const skittles = body
     .filter((sensor) => Object
       .keys(SKITTLES)
       .includes(sensor.mac));

   // Look for skittles with raw data and transform raw data
   if (skittles.length > 0) {
     const skittlesInfo = skittles
       .filter((skittle) => !isUndefined(skittle.rawData)
           && !isNull(skittle.rawData)
           && skittle.rawData.length > 0)
       .map(({ mac, rawData }) => ({
         mac,
         ...advlib.ble.data.process(rawData),
       }))
       .filter((skittle) => hasIn(skittle, 'serviceData.uuid')
           && hasIn(skittle, 'serviceData.data')
           && hasIn(skittle, 'serviceData.minew'))
       .map(({ serviceData: { uuid, data, minew }, ...rest }) => ({
         uuid,
         data,
         ...minew,
         ...rest,
       }))
       .map((data) =>; omit(data, [
         'data',
         'frameType',
         'productModel',
         'accelerationX',
         'accelerationY',
         'macAddress',
         'flags',
         'complete16BitUUIDs',
         'uuid',
       ]));

     console.log({ skittlesInfo });

   } else {
     debug('No skittle sensors', skittles);
   }
 } else {
   debug('No Body or Empty body');
 }
 res.end();
});

 

Nous obtenons donc un tableau d’objets pour chaque beacon reçu :

{ skittlesInfo:
   [ { batteryPercent: 100,
       accelerationZ: 0.9296875,
       mac: 'AC233FA246A2' } ] }

De l’accélération à la position

L’accéléromètre permet de savoir dans quelle direction le beacon se déplace, il ne détecte pas une position mais une accélération sur l’un des trois axes X, Y, Z.

Donc ce que chaque beacon va nous envoyer, ce sont ses changements de vitesses et ses mouvements de translation.

Ce n’est pas suffisant pour ce que nous voulons : savoir si le beacon est horizontal ou vertical ! Heureusement, l’accéléromètre permet aussi de détecter la force de gravité générée par la Terre. C’est en fait ce détail qui va nous être très utile, beaucoup plus que tout le reste. En effet, par chance, la valeur de l’accélération sur l’axe Z est toujours proche de 1 lorsque le beacon ne bouge pas sur cet axe, c’est sa force de gravité. Ainsi nous pouvons déduire qu’il est en position debout.

Nous voulons obtenir un objet avec la structure suivante pour chaque réception :

{ 
    XXXXXXXXXXX: { // adresse MAC
        battery: 100,
        position: 'KNOCKED_OVER', // 'KNOCKED_OVER' ou 'UPRIGHT'
        z: 0.9296875,
        value: 12 // numero de la quille
    },
    ...
}

Si on stocke sur le serveur la dernière position de chaque quille (quasiment en temps réel selon l’intervalle d’envoi réglé sur la gateway), alors nous avons tout ce qu’il faut pour créer une partie de Mölkky.

Considérons donc un objet global sur notre serveur qu’on appelle lastState. Donnons une marge d’erreur via POSITION_LEVEL d’une valeur de 0.8 et ajoutons à la suite de notre code :

skittlesInfo.forEach(({ mac, accelerationZ: z, batteryPercent: battery }) =>; {
  lastState[mac] = {
    ...lastState[mac],
    ...(battery &&; { battery }),
    ...(z && {
      position: z && = POSITION_LEVEL ? 'UPRIGHT' : 'KNOCKED_OVER',
      z,
    }),
    value: SKITTLES[mac],
  };
});

Du socket pour le temps réel

Pour qu’un client puisse se connecter à notre serveur et avoir les données en temps réel, nous utilisons Socket.io pour émettre notre set de quilles avec leur position :

const io = req.app.get('socketio');
io.emit('UPDATE', lastState);

Développer l’application

Le serveur

L’API pour la gestion du jeu

Elle fournit 4 endpoints accessible en POST :

  • /start : pour démarrer une partie
  • /score : pour envoyer le score de l’équipe qui vient de jouer
  • /miss : si une équipe a joué mais n’a pas touché de quilles
  • /reset : pour recommencer la partie ou pour réinitialiser la partie quand elle est terminée
Endpoint Body Réponse
/api/molkky/start
{
  "teams": [
    "cat",
    "dog"
  ],
  "playingTeam": "dog"
}
{
  "scores": {
    "cat": {
      "score": 0,
      "left": 50
    },
    "dog": {
      "score": 0,
      "left": 50
    }
  },
  "currentTurn": {
    "isPlaying": "dog",
    "remain": 3
  }
}
/api/molkky/score
{
  "team": "cat",
  "points": 10
}
{
  "scores": {
    "cat": {
      "score": 0,
      "left": 50
    },
    "dog": {
      "score": 10,
      "left": 40
    }
  },
  "currentTurn": {
    "isPlaying": "cat",
    "remain": 3,
    "wining": "dog"
  }
}
/api/molkky/miss
{
  "team": "dog"
}
{
  "scores": {
    "cat": {
      "score": 0,
      "left": 50
    },
    "dog": {
      "score": 6,
      "left": 44
    }
  },
  "currentTurn": {
    "isPlaying": "cat",
    "remain": 2
  }
}
/api/molkky/reset
{
  "scores": null,
  "currentTurn": null
}

Nous avons documenté ces endpoints et leurs réponses potentielles sur Postman.

Un singleton pour la partie en cours

Un singleton CurrentGame est instancié lorsque le endpoint /start est appelé. C’est cette instance qui contient toutes les infos de la partie en cours.

Les routes pour servir le front

Enfin les routes /molkky/game et /molkky/play rendent le composant front Game en lui fournissant le currentGame en contexte.

Schema Node JS Experss serveur API et Socket.io

Le client pour les joueurs

Ici nous avons réalisé l’application front avec React. Le composant Game (que nous avons vu plus tôt, en server side rendering) :

  • Un DataContext qui contient les données des quilles, récupérées via Socket.io.
  • Un PlayContext qui contient les données propres à la partie.
  • Selon la route :
    • Un composant StartScreen qui permet à l’utilisateur de sélectionner la team qui va jouer et vérifier que toutes les quilles soient détectées et en position debout avant de pouvoir commencer la partie.
    • Un composant PlayScreen qui contient les composants du jeu : affichage des quilles, score, bouton de validation du score, modale pour modifier éventuellement le score.

Schema app front avec React

Screenshots d’une partie

Molkky connecté - StartScreen avec quille maquante
Molkky connecté - StartScreen ready
Molkky connecté - PlayScreen jeu en cours
Molkky connecté - PlayScreen jeu en cours points marques
Molkky connecté - PlayScreen modale d'ajustement du score
Molkky connecté - PlayScreen game over

Le Mölkky en situation réelle

Intégrer nos beacons à nos quilles

Les essais

Avant d’installer nos beacons dans des quilles en bois, nous avons fait des tests. Les images se passent de commentaires.

Prototype du Molkky

Les quilles en bois

Un collègue doué en bricolage + un set de Mölkky en bois = le tour est joué !

Insertion du beacon dans une quille en bois Insertion du beacon dans une quille en bois Insertion du beacon dans une quille en bois

Bilan

Il faut améliorer la fiabilité

À l’usage, sur un stand de la Xebicon, nous avons observé que parfois les beacons ne captaient pas le fait d’avoir été renversés ou relevés. Est-ce dû au choc ? Faut-il améliorer les réglages des beacons ?

De plus, le fait d’avoir un système où l’envoi de POST est multiple en permanence, n’est pas des plus performants. On aurait préféré une solution qui va chercher l’information sur le device bluetooth, plutôt que celui-ci n’émette en permanence. D’autres technologies sont peut-être plus appropriées. Aussi faudrait-il approfondir et surveiller ce que donne l’API Web Bluetooth.

En conclusion, on peut dire que pour des beacons, la fiabilité est:

  • raisonnable car nous avons pu faire plusieurs parties
  • insuffisante car nous avons du plusieurs fois corriger la détection

Il faut itérer pour développer des features

Nous avons implémenté un leaderboard pour la journée sur le stand de la Xebicon pour enregistrer les scores via Firebase.

Beaucoup d’améliorations et de nouvelles features sont envisageables :

  • settings des règles de jeu
  • prendre en compte des joueurs individuels et plus de 2 teams
  • jouer avec les statistiques de jeu
  • etc.

Si l’envie vous prend de vouloir tester, les sources sont disponibles sur le projet Github Molkky connecté, avec un menu qui permet de simuler des beacons si vous n’en avez pas.

Le mot de la fin

On ne va pas se mentir : notre Mölkky est loin d’être prêt à être commercialisé. Se balader sur les terrains de jeu avec une gateway branchée qui a besoin d’Internet c’est pas l’idéal. Ce n’est pas facilement portable et si vous êtes à Poiseul-la-Grange c’est même un peu difficile de trouver de la 3G.

Couverture 3g inexistante en France
Mais chez Publicis Sapient Engineering, on aime les projets impossibles et surtout on aime chercher des solutions. La Xebicon c’est l’occasion de partager la recherche qu’on fait grâce à ces projets insensés qui nous passionnent. Et qui sait, peut-être vous donner envie de participer pour les améliorer ?

Publié par

Publié par Jennifer Proust

Jennifer est dev front end consultante chez Publlicis Sapient depuis 2018. Elle travaille surtout avec React et s'intéresse aussi aux autres frameworks front ainsi qu'à Node. Elle porte une attention particulière aux tests et au suivi de la qualité de code.

Commentaire

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Sapient, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.