Logo Spiria

Laveuse connectée avec Raspberry Pi

11 avril 2016.

J’ai reçu en cadeau l’année dernière un Raspberry Pi B+ avec tout un assortiment d’accessoires (plaquette de prototypage, interface réseau sans-fil, DELs, boutons-poussoirs, etc.). Je n’avais franchement aucune idée de quoi en faire. Je l’ai utilisé comme centre multimédia un certain moment, mais comme j’avais déjà un Apple TV, cela me semblait redondant.

Puis, un jour, alors que j’oubliais pour une énième fois la lessive dans ma laveuse, faute d’avoir entendu l’alerte sonore de fin de cycle (ladite laveuse se situant dans mon garage), j’ai fait “1+1” : j’allais me servir de mon Raspberry Pi pour régler ce problème.

Recherche

J’ai dès lors commencé une petite recherche sur Internet pour me mettre un peu plus au courant des possibilités qui m’étaient disponibles avec le Raspberry Pi. En cherchant pour des projets similaires, j’ai vu que différentes personnes ayant tenté l’expérience avaient approché le problème de différentes manières :

  1. Certains utilisaient un système basé sur un détecteur de vibrations.
  2. Certains utilisaient un détecteur de luminosité pour vérifier l’état de l’indicateur de marche de la laveuse.
  3. D’autres tentaient de se brancher directement sur le circuit électronique de la laveuse.

Il était hors de question que je risque de briser ma laveuse en bidouillant dans ses circuits électroniques, alors l’option 3 n’était pas pour moi. Ensuite, ceux qui utilisaient l’option 2 devaient souvent coller de l’appareillage à l’avant de la laveuse, ce qui me semblait bien inélégant.

Il me restait donc l’option 1, celle du détecteur de vibrations.

Détecteur de vibrations

Ici encore, j’avais un choix à faire parmi plusieurs possibilités. Il existe différentes technologies de détecteurs de vibrations : électromécanique, piézoélectrique, optique…

J’ai choisi un peu au hasard, je l’avoue, d’utiliser un détecteur de vibration électromécanique. Le composant principal d’un tel détecteur est l’interrupteur à senseur de vibrations SW-18010P.

sw18010p

Il s’agit d’un composant très simple, consistant en une tige métallique se situant au centre d’un ressort. Quand le composant vibre, le ressort fait contact avec la tige métallique, ce qui ferme le circuit électrique.

Afin d'adapter le signal généré par le composant aux niveaux de tension requis par les entrées numériques du Raspberry Pi, j'ai choisi d'utiliser un petit circuit de conditionnement du signal.

N’ayant pas conçu de circuits électriques depuis l’université, je me suis tourné une fois de plus vers Internet pour m’indiquer comment utiliser ce composant avec le Raspberry Pi. J’ai trouvé sur Amazon un petit module pour environ 5 dollars qui combinait le SW-18010P avec un comparateur de tension LM393.

Ce module peut être directement alimenté par le Raspberry Pi (3.3 V), et son signal peut être directement fourni à l’entrée de celui-ci, ce qui répondait à mes besoins.

Notifications « push »

Le but premier du projet étant de me notifier à distance quand le cycle de lavage est terminé, je devais trouver une façon simple (autant que possible) d’envoyer des notifications à partir de mon Raspberry Pi jusqu’à mon téléphone. J’avais déjà utilisé un service de notifications push nommé Pushover pour un projet antérieur, et je savais déjà comment il fonctionnait. J’ai donc décidé de réutiliser mon compte existant sur ce service pour le projet.

Il existe beaucoup de services concurrents. Certains demandent des frais mensuels pour avoir le droit d’envoyer un certain nombre de notifications push. Le service Pushover propose plutôt de payer une seule fois pour une application (pour téléphone intelligent/tablette, ou pour desktop), ce qui donne le droit à un certain nombre de notifications gratuites par mois. Pour mes besoins non-commerciaux, cela me semblait plus approprié.

Une fois mon compte créé sur le site, une clé d’utilisateur m’a été attribuée. Cette clé est nécessaire pour recevoir les notifications sur mes appareils. Ensuite, pour chaque application ou script que je crée et qui enverra des notifications, je dois créer une clé d’application. Pour ce projet, il a donc suffi que je crée une nouvelle clé d’application. Voici un aperçu de la page décrivant mon compte Pushover :

L’envoi d’une notification à partir de Python consiste en l’envoi d’un message POST (HTTP) contenant ma clé d’usager, la clé d’application et le message à afficher.

Conception du système

Une fois le composant reçu, il m’a fallu penser à ce dont j’avais besoin pour produire ma solution complète. Je voulais :

  1. Être capable d’indiquer au Raspberry Pi qu’un cycle de lavage commençait (pour éviter qu’il ne détecte des vibrations ambiantes comme étant des cycles de lavage).
  2. Avoir une certaine rétroaction visuelle confirmant le fonctionnement normal du système (puisqu’aucun écran ne sera connecté au Raspberry Pi).
  3. Recevoir une alerte sur mon téléphone mobile une fois le cycle de lavage terminé.

En fouillant dans mon petit ensemble de composants, je trouvai ce dont j’avais besoin : un bouton-poussoir pour démarrer le cycle, et une DEL pour m’indiquer que le Raspberry Pi était bien en train de monitorer les vibrations de la laveuse.

Et comme l’ensemble de composants que j’ai reçu avec mon Raspberry Pi contenait un adaptateur WiFi, je n’avais aucun problème pour envoyer via Internet mes notifications.

Familiarisation avec le Raspberry Pi

La première étape à proprement parler était d’installer un système d’exploitation sur la carte micro SD de mon Raspberry Pi. Cela s’est avéré très facile : le site raspberrypi.org fournit le nécessaire pour créer une carte micro SD contenant le programme d’amorçage NOOBS qui permet de choisir un système d’exploitation à installer sur la carte. J’ai décidé d’installer le système d’exploitation Raspbian, puisqu’il était basé sur Linux et semblait contenir une bonne partie de ce dont j’aurais besoin pour mon projet dès le départ.

Une fois Raspbian installé, j’ai fait le tour de ce qui était installé avec le système pour avoir une idée des possibilités. J’ai vite vu que Python était préinstallé, ce qui me semblait être une plateforme de choix pour développer mon petit projet. Une courte recherche sur le web me permit de trouver la librairie RPi.GPIO qui permet de contrôler les différentes entrées et sorties numériques du Raspberry PI.

J’ai commencé par essayer de contrôler l’allumage d’une DEL pour me familiariser avec la librairie. Pour ce faire, j’ai connecté la DEL au Raspberry Pi avec une résistance en série pour fournir le courant approprié nécessaire à son allumage. Le circuit ressemblait à ceci :

La librairie RPi.GPIO étant très facile à utiliser, j’ai pu faire fonctionner mon petit prototype avec très peu de lignes de code :

try:
    # importation des librairies nécessaires
    import RPi.GPIO as GPIO
    import time

except RuntimeError:
    print("Error loading RPi.GPIO")

# définition de la broche de sortie connectée à la DEL
LED = 37

def main():
    try:
        # configuration des broches GPIO
        GPIO.setmode(GPIO.BOARD)
        GPIO.setup(LED, GPIO.OUT)

        while True:
            # après 2 secondes, allumer la DEL (en lui fournissant un GND)
            time.sleep(2)
            GPIO.output(LED, False)

            # après 2 secondes, éteindre la DEL (en lui fournissant 3.3V)
            time.sleep(2)
            GPIO.output(LED, True)
    except:
        print(“Quit on exception”)
    finally:
        GPIO.cleanup()

if __name__ == '__main__' : main()

Mon premier test étant concluant, je pouvais alors passer aux choses sérieuses.

Circuit final

Maintenant que je savais comment contrôler la librairie RPi.GPIO, il ne me restait plus qu’à intégrer dans mon circuit le bouton-poussoir pour démarrer le cycle de lavage, puis le détecteur de vibrations pour savoir quand le cycle est terminé.

Le bouton poussoir a une connexion normalement ouverte, qui se ferme (établit la connexion) quand on appuie sur le bouton. Une des terminaisons du bouton doit être connectée à l’alimentation (3.3v venant du RPi), et l’autre, à une des broches GPIO du RPi (j’ai choisi la broche 33 — GPIO 13). De cette façon, une pression du bouton-poussoir enverra un signal de 3.3v au RPi. Ceci n’est par contre pas suffisant pour assurer un bon fonctionnement du système. Le problème est qu’utilisé tel quel, lorsque le bouton-poussoir est en position normale (circuit ouvert), la broche 33, qui est configurée en tant qu’entrée, n’est plus connectée à rien. Elle se trouve donc en état flottant, soit ni à 3.3v (niveau haut), ni au GND (niveau bas). Il existe une façon matérielle de régler ce problème, soit en connectant entre la broche 33 et le GND une haute résistance (appelée « pull-down »). Cependant, le Raspberry Pi fournit déjà implicitement des résistances « pull-down » et « pull-up » sur chacune de ses broches GPIO. Il est donc possible de configurer ces broches à partir du logiciel pour les connecter à une résistance interne au RPi, ce que je vais faire plus bas.

Quant au détecteur de vibrations, son fonctionnement est on ne peut plus simple. Il suffit de l’alimenter à même le Raspberry Pi, puis de brancher sa broche de sortie à une des broches GPIO du RPi, configurée en tant qu’entrée.

Voici à quoi ressemble le circuit final :

Et le code :

try:
    # Importation des librairies nécessaires
    import RPi.GPIO as GPIO
    import time
    import http.client, urllib # pour les notifications push
    import logging             # pour le débogage
except RuntimeError:
    print("Error loading RPi.GPIO")

DELAYINSECS = 180 # Délai en secondes avant de déclarer le cycle terminé

# Définition des broches d'entrées/sorties du RPi
BUTTON = 33
LED = 37
VIBRATION = 31

# Fonction qui envoie une notification push à l'aide de pushover.net
def pushdone():
    conn = http.client.HTTPSConnection("api.pushover.net:443")
    conn.request("POST", "/1/messages.json",
        urllib.parse.urlencode({
            "token": "",
            "user":  "",
            "message": "Le lavage est prêt",
        }), { "Content-type": "application/x-www-form-urlencoded" })
    conn.getresponse()

def main():
    try:
        # Configuration des broches GPIO
        GPIO.setmode(GPIO.BOARD)
        GPIO.setup(VIBRATION, GPIO.IN)
        GPIO.setup(LED, GPIO.OUT)

        # Une résistance "pull-down" doit être ajoutée à l'entrée du 
        # bouton poussoir pour éviter d'avoir une valeur flottante à 
        # l'entrée du RPi quand le bouton n'est pas en position fermée.
        GPIO.setup(BUTTON, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

        # Début de l'observation de tout front montant ou descendant sur 
        # la broche connectée au détecteur de vibrations. L'argument 
        # "bouncetime" permet d'ignorer l'effet de rebondissement causé 
        # par le changement d'état soudain.
        GPIO.add_event_detect(VIBRATION, GPIO.BOTH, bouncetime=200)

        # Configuration d'un fichier journal de débogage sur le RPi
        logging.basicConfig(filename='/home/pi/washer.log', 
                            level=logging.INFO, 
                            format='%(asctime)s %(levelname)s:%(message)s')
        logging.info("****************************")
        stop = False
        logging.info("Entering main loop")

        # Boucle principale qui attendra que le bouton-poussoir soit 
        # enfoncé pour indiquer le début du cycle, puis débutera la 
        # vérification périodique de la vibration.
        while not stop:
            logging.info("Main loop iteration")
            GPIO.output(LED, True) # LED éteinte
            GPIO.wait_for_edge(BUTTON, GPIO.RISING) # attente d'un signal 
                                                    # du bouton poussoir
            logging.info(" Started")
            going = True
            GPIO.output(LED, False) # LED allumée

            # Boucle secondaire du programme qui vérifiera chaque 
            # 3 minutes si une vibration a eu lieu durant ce délai. 
            # Si la vérification ne rapporte pas de vibration pendant 
            # 3 minutes, le cycle est considéré terminé.
            while going:
                logging.info("  Inner loop iteration")
                time.sleep(DELAYINSECS)
                logging.info("  Just slept %ds", DELAYINSECS)
                
                # Dispositif de contournement pour arrêter le cycle 
                # courant manuellement en gardant le bouton-poussoir 
                # appuyé durant la vérification.
                if GPIO.input(BUTTON):
                    stop = True
                    going = False

                # Fin du cycle si aucune vibration n'a été détectée.
                if not GPIO.event_detected(VIBRATION):
                    logging.info("  Stopped vibrating")
                    pushdone()
                    going = False
            logging.debug(" End of iteration")
    except:
        logging.warning("Quit on exception")
    finally:
        logging.info("Cleaning up")
        GPIO.remove_event_detect(VIBRATION)
        GPIO.cleanup()

if __name__ == '__main__': main()

Une fois le code écrit dans un fichier texte (/home/pi/washer_cycle.py), il suffisait d’ajouter au script de lancement de Raspbian (/etc/rc.local) des instructions pour lancer le script en mode superutilisateur dès le démarrage du système (la librairie RPi.GPIO a quelques problèmes de compatibilité en mode utilisateur, d’où la nécessité d’exécuter la commande avec sudo).

  • À la fin de /etc/rc.local :
/home/pi/script_auto_run
  • Dans /home/pi/script_auto_run :
#!/bin/bash
echo "Autorun washer script"
sudo /usr/bin/python3 /home/pi/washer_cycle.py

Test ultime

Il est maintenant temps de tester l’appareil en situation réelle ! J’ai installé mon appareil sur le dessus de ma machine à laver, lancé un cycle de lavage et appuyé sur le bouton poussoir. La DEL s’est allumée pour m’indiquer le bon fonctionnement du script.

Finalement, après environ une heure, j’ai bel et bien reçu une notification sur mon téléphone :