Transformez une roue IKEA LUSTIGT en une roue connectée WiFi 🎡

par

Baptiste

Baptiste Adrien

9 minutes de lecture

Transformez une roue IKEA LUSTIGT en une roue connectée WiFi 🎡

Chez Premier Octet, bien que nous soyons spécialisés dans le développement d'applications React et React Native, rien ne nous empêche de nous glisser sporadiquement dans le trépident domaine de l'IoT. Il y a quelques semaines, je me suis rendu chez IKEA pour y acheter des panneaux SKÅDIS et manger des hot-dogs 🌭. Comme l'impose la conception architecturale d'un IKEA, je suis passé par tous les endroits possibles et imaginables du magasin et notamment celui des enfants, pour tomber nez à nez avec LUSTIGT, une "roue de la chance" en bois. Ni une, ni deux, je me suis dit qu'il fallait ✨ la connecter à Internet ✨.

Quelques hot-dogs plus tard, je repars donc, avec LUSTIGT (et SKÅDIS) dans les bras avec la ferme intention de hacker ce beau jouet.

TL;DR



Trouver le hack

Les choses sérieuses commencent : dans l'idéal je souhaite pouvoir déclencher un webhook contenant la valeur de la case une fois la roue tournée. J'envisage donc d'utiliser 24 émetteurs pour chaque case ainsi qu'un capteur maître pour les lire. Ils doivent être sans fil en raison de la mobilité de la roue. Le capteur maître doit lire les valeurs et les envoyer via une connexion WiFi. Autre contrainte, les émetteurs sur la roue doivent être assez fins : l'espace étant inférieur à 2cm…

Cela semble le job parfait pour du RFID !

La RFID

La RFID utilise des champs éléctro-magnétiques pour identifier des tags. Ceux-ci contiennent une puce électronique leur permettant de recevoir et de répondre aux requêtes radio émises depuis l’émetteur. Les tags existent sous de nombreuses formes : cartes, porte-clés, badges mais aussi sous forme de stickers. Ces-derniers sont idéaux pour ce projet, car ils sont :

  • Très fins (< 1mm) ;
  • Passifs (auto-alimentés par le champ radio) ;
  • Facilement positionnables ;
  • Peu onéreux (environ 20€ les 50 stickers).

L'idée est donc de coller 24 stickers derrière chaque case de la roue et d'ajouter un lecteur RFID à sa base. Et pour envoyer les données à Internet ? Un Photon !

Le Photon

Il y a 4 ans, j'ai découvert le Photon : je n'ai depuis plus utilisé d'Arduino. Comme ce-dernier, le Photon est un micro-contrôleur avec beaucoup d'avantages :

  • WiFi embarqué ;
  • Facilement programmable (comme l'Arduino) ;
  • Même PINs que l'Arduino ;
  • Accessible via une API ;
  • Web based IDE ;
  • Très bonne documentation ;
  • Open-source ;
  • Petit et accessible (\$19).

Le Photon va nous permettre de contrôler le système RFID et d'envoyer les données à Internet.

Matériel nécessaire

Voici la liste du matériel nécessaire, comptez environ 60€ pour le tout :

Matériels

Et comme autres outils (que vous devriez avoir sous la main) :

  • Fer à souder ;

  • Du double face ;

  • Une alimentation 5V micro USB (type Raspberry).

Le montage du hardware

Le montage est facile : il s'agit uniquement de souder les headers sur votre carte RFID afin de la connecter facilement au Photon. Pour cela insérez le header (celui coudé) dans les slots de la carte RC522, et faites-y 6 points de soudure :

Matériels

Brancher les cartes

Une fois le header soudé, vous pouvez relier la carte RFID à votre Photon, pour cela utilisez les câbles femelles en suivant ce schéma :

Function     RC522 Pin     Photon Pin
-------------------------------------
Reset        RST           D2
SPI SS       SDA           D1
SPI MOSI     MOSI          A5
SPI MISO     MISO          A4
SPI SCK      SCK           A3
IRQ          IRQ           -
GROUND       GND           GND
POWER        3.3V          3.3V # 🚨 Not 5V!

PCB

Attention, la carte RFID ne supporte pas le 5v, veillez à bien la brancher sur la pin 3.3V du Photon. Je me suis limité à ce montage qui est très simple, pour les plus assidus vous pouvez créer votre propre PCB en passant par exemple par Eagles.

Configurer votre Photon

Si vous n'avez pas encore configuré votre Photon, suivez les instructions sur https://setup.particle.io :

PCB

Une fois votre Photon connecté à Internet et configuré, il est temps de faire un peu de collage…

Coller les stickers RFID

La stack hardware est prête, il nous faut maintenant coller nos stickers RFID derrière chaque case de la roue. Veillez à bien les placer au centre des deux bâtons à chaque fois pour éviter des collisions d'UID lors de la lecture des tags.

Matériels

Fixer la carte RFID

Il ne reste plus qu'à fixer la carte RC522 sur la base de la roue, pour cela j'ai simplement utilisé du double face et fixé le Photon derrière :

Montage

Code côté hardware

Il est maintenant temps de coder ! Nous allons envoyer notre programme à notre Photon afin de piloter notre stack RFID. Si vous n'êtes pas familier avec l'écosystème de Particle, sachez qu'il dispose d'un IDE en ligne permettant d'envoyer vos sketchs (au format .ino) directement sur votre Photon via le WiFi.

Lecture des tags UID

Il existe déjà une librairie pour s'interfacer avec le module RFID, vous n'avez donc qu'à l'ajouter puis l'importer dans votre projet.

Voici le code minimal pour lire les UIDs de chaque tag :

#include "MFRC522/MFRC522.h"
#define SS_PIN D1
#define RST_PIN D2

MFRC522 mfrc522(SS_PIN, RST_PIN);

void setup() {
  mfrc522.setSPIConfig();
  mfrc522.PCD_Init();
}

void loop() {
  if (!mfrc522.PICC_IsNewCardPresent() && !mfrc522.PICC_ReadCardSerial()) {
    return
  }

  String UID = "";

  for (byte i = 0; i < mfrc522.uid.size; i++) {
    UID += String(mfrc522.uid.uidByte[i] < 0x10 ? "0" : "");
    UID += String(mfrc522.uid.uidByte[i], HEX);
  }

  mfrc522.PICC_HaltA();
  Particle.publish("WHEEL_VALUE", UID, 5, PRIVATE);
}

Le cloud Particle nous permet de partager facilement des données via la méthode Particle.publish(). Une fois publié vous pouvez voir l'évenement WHEEL_VALUE dans la partie event de la console :

Event dahsboard

Vous pouvez alors tourner votre roue afin de lire un par un les UIDs de vos tags et les classer par ordre croissant dans un tableau :

String wheelCases[] = {
    "044653120a3c80", // case 1
    "043653120a3c80",
    "04513d92ec5a80",
    "043c4892ec5a81",
    "04374892ec5a81",
    "04454792ec5a81",
    "043f4e92ec5a81",
    "04244e92ec5a81",
    "04414892ec5a81",
    "04294e92ec5a81",
    "042e4e92ec5a81",
    "04354e92ec5a81", // case 12
    "043a4e92ec5a81",
    "04444e92ec5a81",
    "046f4e92ec5a81",
    "044c4c92ec5a81",
    "045f4392ec5a80",
    "04484d92ec5a81",
    "04e64992ec5a80",
    "04514c92ec5a81",
    "04564c92ec5a81",
    "04f04992ec5a80",
    "043e53120a3c80",
    "042d53120a3c80" // case 24
};

Après cette tâche un peu fastidieuse, nous allons améliorer notre code afin qu'il n'envoie un événement uniquement lorsque la roue s'arrête de tourner (une sorte de debouncing).

Implémenter la logique

Voici mon algo pour envoyer uniquement la dernière valeur : lorsque aucune valeur n'a été lue depuis 1s, nous publions l'évenement avec Particle.publish(). Je publie également un évenement SPINNING afin de rajouter un feedback côté UI :

#include "MFRC522/MFRC522.h"
#define SS_PIN D1
#define RST_PIN D2

MFRC522 mfrc522(SS_PIN, RST_PIN); // Create MFRC522 instance.

String wheelCases[] = {
   // Ajouter votre mapping d'UIDs (voir ci-dessus)
};

long chrono = 0;
long debounceDelay = 1000;

bool isInit = true;
bool isSpinning = false;

String valueToCommit = "";

void setup() {
  mfrc522.setSPIConfig();
  mfrc522.PCD_Init();
}

void loop() {
    if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
        if (!isSpinning) {
            Particle.publish("SPINNING", "true", 5, PRIVATE);
            isSpinning = true;
        }

        String UID = "";
        String label = "";

        for (byte i = 0; i < mfrc522.uid.size; i++) {
          UID += String(mfrc522.uid.uidByte[i] < 0x10 ? "0" : "");
          UID += String(mfrc522.uid.uidByte[i], HEX);
        }

        mfrc522.PICC_HaltA();

        for (int i=0; i<24; i++){
            if (wheelCases[i] == UID){
                label = i + 1;
            }
        }

        if (!isInit) {
            valueToCommit = label;
            chrono = millis();
        }

        isInit = false;
    }

    if (valueToCommit != "" && (millis() - chrono) > debounceDelay) {
        Particle.publish("WHEEL_VALUE", valueToCommit, 5, PRIVATE);
        Particle.publish("SPINNING", "false", 5, PRIVATE);

        valueToCommit="";
        isSpinning = false;
    }
}

Applications

Notre roue est prête ! Elle envoie désormais un évenement SPINNING dès que la roue tourne et la valeur de la dernière case via l'évenement WHEEL_VALUE. Il est maintenant temps d'utiliser ces évenements !

Hook everything

Grâce à la platforme Particle Web, nous pouvons facilement lier des évenements à des webhooks grâce à la partie intégration de la Particle Console :

Particle Console

Nous pouvons ainsi poster la valeur tirée au sort sur Slack en appelant un incoming webhook lors d'un évenement WHEEL_VALUE.

Autre bonne nouvelle : IFTTT offre une intégration de la platforme Particle permettant de connecter n'importe quel évenement à l'ensemble des services IFTTT : Slack, Google Doc, Twitter… Comme par exemple, insérer la valeur de chaque tirage dans un Google spreadsheet, ou bien la tweeter :

IFTTT Console

Application React

Chez Premier Octet, le React c'est notre dada… Nous n'allions pas passer à côté d'une occasion de développer une application affichant le résultat de notre tirage en temps réel !

Particle met à disposition une librairie JS permettant d'utiliser le Particle Cloud : particle-api-js. Nous allons donc pouvoir souscrire à nos évenements SPINNING et WHEEL_VALUE avec la méthode getEventStream() :

import React, { Component } from 'react';
import Particle from 'particle-api-js';

const DEVICE_ID = 'XXXXX';
const TOKEN = 'XXXXX';
const particle = new Particle();

class App extends Component {
  state = { isSpinning: false, event: null };

  componentDidMount() {
    particle.getEventStream({ deviceId: DEVICE_ID, auth: TOKEN }).then(stream => {
      stream.on('event', event => {
        if (event.name === 'SPINNING') {
          this.setState({ isSpinning: true });
        } else if (event.name === 'WHEEL_VALUE') {
          this.setState({ event: event.data, isSpinning: false });
        }
      });
    });
  }

  render() {
    return (
      <div className="app">
        <div className="caption">
          {this.state.isSpinning ? (
            <span>Spinning ✨️</span>
          ) : (
            <>{this.state.event ? <span>🕺 {this.state.event} 🕺</span> : <span>Spin the wheel! 🤸‍♀️</span>}</>
          )}
        </div>
      </div>
    );
  }
}

Nous affichons maintenant la dernière valeur sur laquelle s'est arretée la roue. C'est bien mais l'idée initiale est de pouvoir tirer au sort une valeur parmi un groupe de choix : restaurants, noms de personnes…

Nous allons donc créer un spreadsheet sur Google Doc afin de pouvoir mapper nos cases à nos labels. Il suffit de créer un classeur pour chaque jeu de données (foods, users) :

Google spreadsheets

… et de les lier grâce à l'API de Google Spreadsheet (nécessite de créer une clé d'API) :

const API_KEY = 'xx';
const SPREADSHEET_ID = 'xx';
const SHEET_NAME = 'food';

loadLabels = async () => {
  const data = await fetch(
    `https://sheets.googleapis.com/v4/spreadsheets/${SPREADSHEET_ID}/values/${SHEET_NAME}!a1:a26?key=${API_KEY}`,
  ).then(res => res.json());

  return data.values;
};

Vous pouvez ensuite ajouter dans votre UI, un select pour choisir le jeu de données voulu avant de lancer la roue 🎡. Retrouvez le code complet sur le repository GitHub.

Hack hack hack

Connectez la roue IKEA LUSTIGT aux Internets a été drôlement intéressant, j'ai été surpris que tout ce beau monde s'imbrique correctement et marche du premier coup… Si vous en avez encore sous le pied, vous pouvez aller encore plus loin :

  • ajouter un feedback sur la roue lorsqu'elle tourne (LED, musique…) ;
  • faire une intégration plus propre du Photon et de la board RFID sur la roue (👋imprimante 3D) ;
  • trouver d'autres applications !

Si vous montez votre propre roue, envoyez-nous vos photos et applications !

Happy hacking ! 🎈

Continuer la discussion sur Twitter

18 avenue Parmentier
75011 Paris
+33 1 43 57 39 11
hello@premieroctet.com

Suivez nos aventures

GitHub

Naviguez à vue