IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Python Flask et SQLite pour le Raspberry Pi 3 ou 4 - exemples de communication avec des ESP32 ou ESP8266

Cet article nous montre comment, sur un Raspberry Pi, installer et utiliser Flask, un environnement de développement Web sous Python. Flask sera principalement utilisé comme outil de communication pour des événements venant de l’extérieur, comme des microcontrôleurs ESP8266 et ESP32 et pour montrer comment enregistrer ces événements dans une base de données SQLite.

1 commentaire Donner une note à l´article (5)

Article lu   fois.

L'auteur

Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Cet article contient un nombre incroyable de technologies, de langages de programmation et de sujets divers. Il y a ici tellement d’informations qu’il faudra plus les considérer comme des introductions à tous les domaines traités.

Je ne suis pas moi-même un expert en Python et je maîtrise plus des langages comme Java, C et C + +. Étant par conséquent un « amateur pythonique », il faudra sans doute consulter les sites de référence du langage. Cependant, sur les parties où j’ai moi-même croché ou transpiré, j’ai évidemment documenté ces morceaux de code délicats. Cela devrait suffire pour un débutant en Flask ! Le lecteur devra sans aucun doute se documenter, mais avec des exemples tous parfaitement fonctionnels.

Quoique l’accès au GPIO du Raspberry Pi utilise souvent la version 2 du langage Python, je n’utiliserai ici que la version 3, donc la commande python3 sur le Raspberry Pi.

Le Raspberry Pi est une plateforme exceptionnelle pour l’apprentissage du langage Python pour programmer le port GPIO où viendra se loger des composants comme des capteurs, des LED ou autres composants comme des buttons ou des buzzers. Ces aspects seront mineurs ici, car je me suis plus intéressé à la communication avec l’extérieur, c’est-à-dire depuis des microcontrôleurs Wifi du type ESP32 ou ESP8266. Le Raspberry Pi peut être considéré comme un nano-ordinateur, alors qu’un ESP8266 bon marché est le choix idéal pour mesurer, par exemple, une température dans une pièce et l’envoyer à un Raspberry Pi avec le framework Flask.

Le lecteur connaît sans doute déjà ce fameux nano-ordinateur dont voici un des modèles, le Raspberry Pi 3 B :

Image non disponible

Voici déjà présentés rapidement, quelques thèmes couverts dans cet article.

  • Installation de Python Flask sur le Raspberry Pi 3 ou 4.
  • Première utilisation de Flask.
  • Démarrage automatique d’un script Flask.
  • Premiers tests.
  • PyDev sous Windows Eclipse.
  • Flask Templates.
  • Exemple avec Relais et Buzzer (schéma Fritzing).
  • GET d’un ESP32 et des accès GPIO.
  • PIR et Buzzer (schéma Fritzing avec ESP32).
  • Pi Reboot avec Flask.
  • Get depuis un PC Windows (Python, Java).
  • SQLite, Flask et température.
  • GET d’un ESP8266 (mesure de températures).
  • Envoi par courriel des températures extrêmes d’une journée.

Nous allons montrer ici qu’un serveur Web Flask peut être utilisé comme outil de communication pour transférer des données entre Raspberry Pi ou d’autres plateformes informatiques comme des Arduino, ESP, voire des PC ou smartphones.

Des solutions en Java, avec un serveur Web et une base de données SQLite, sont aussi possibles et décrites dans mon livre publié chez Eyrolles, UN LIVRE SUR JAVA, PYTHON, ECLIPSE ET LE RASPBERRY PI 3 (exploitable aussi pour le Raspberry Pi 4). Ce livre est un peu de même facture que cet article, mais avec évidemment plus de détails et de précisions qu’ici. Je suis aussi actif ici sur developpez.net, en particulier dans les groupes d’intérêt dédiés aux Raspberry Pi, Arduino et ESP32/ESP8266. J’y ai d’ailleurs déjà publié les articles :

Cet article est consacré à l’installation et l’utilisation d’un serveur Web Flask pour le Raspberry Pi. En même temps, il pourrait permettre aux lecteurs qui n’ont presque aucune notion en programmation Python de se familiariser avec ce langage. De petits exemples simples en Python, soit pour le framework Flask, soit pour contrôler les broches GPIO du Raspberry Pi, seront présentés et expliqués ici.

L’installation a été faite sur un Raspberry Pi 4, car je l’avais reçu récemment, en juillet 2019, à sa sortie. Son utilisation sur un Raspberry Pi 3 B est identique.

Je répéterai ici la définition de Flask qu’on trouve sur Wikipédia :

Flask est un framework open source de développement web en Python. Son but principal est d’être léger, afin de garder la souplesse de la programmation Python, associé à un système de templates. Il est distribué sous licence BSD2.

Nous supposerons ici que le lecteur a déjà son Raspberry Pi installé avec le système d’exploitation standard Raspbian ou Buster et qu’il a déjà un peu d’expérience avec les broches GPIO, des capteurs ou autres matériels classiques d’un environnement Raspberry Pi.

Pour les ESP32 et ESP8266 utilisés dans cet article, je ne donnerai aussi que peu d'explications sur les broches et leurs utilisations. De simples recherches sur le Web nous permettront de découvrir les détails de montages pour des composants ou circuits équivalents. Suivant les constructeurs, il peut y a voir de petites différences de notations et de numérotations GPIO. Le lecteur pourra très bien, avec un peu d'attention, choisir d'autres versions d'ESP que celles indiquées.

Comme j’utilise mes Raspberry Pi sans y brancher aucun écran ni clavier, toutes les procédures d’installation et de vérification sont exécutées dans une console du système d’exploitation, le Raspian Buster ici. La console apparaît chez moi à partir d’une connexion PuTTY sur un PC Windows 10. Tous les tests de pages accessibles sur le serveur Web Flask sont faits depuis un navigateur Chrome ou Firefox (mais doivent fonctionner aussi avec par exemple Edge ou Opera). L’application Windows PuTTY peut être installée après téléchargement depuis https://www.putty.org/.

Attention à la configuration du Raspberry Pi : il faudra s’assurer que le fuseau horaire est correctement défini. Nous pourrons faire la vérification avec sudo raspi-config, le menu 4 (Localisation Options), ensuite I2 (Change Timezone) et mettre, par exemple, sur Europe et Paris. Si ce n’est pas juste, les dates en Python seront décalées et des heures incorrectes stockées dans la base de données SQLite.

I-A. Références sur le Web

Il y a beaucoup de références sur le Web et en voici quelques-unes pour bien débuter :

II. Installation de Flask

Qui dit Flask, dit serveur Web. Donc la connaissance de l’adresse IP de notre Raspberry Pi est essentielle pour tous les accès de l’extérieur. Une console PuTTY sur notre Pi se présentera ainsi :

 
Sélectionnez
login as: pi
pi@192.168.1.143's password:
Linux raspberrypi 4.19.50-v7l+ #895 SMP Thu Jun 20 16:03:42 BST 2019 armv7l
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Fri Sep  6 09:07:26 2019 from 192.168.1.110
pi@raspberrypi:~ $

L’adresse IP 192.168.1.143 du Raspberry Pi 4 a été définie lors de l’installation et peut être attribuée comme adresse IP fixe dans le routeur domestique.

Si nous travaillions depuis un PC Linux, par exemple Ubuntu, nous pourrions alors utiliser la commande ssh équivalente à l’application Windows PuTTY :

ssh pi@192.168.1.143

Si notre Raspberry Pi n’a pas été installé récemment, il faudrait tout d’abord le mettre à jour. Ces deux commandes peuvent être exécutées de toute manière, sans risque, pour des mises à jour éventuelles :

pi@raspberrypi ~ $ sudo apt-get update

pi@raspberrypi ~ $ sudo apt-get upgrade

Les deux commandes d’installation requises pour Flask sont :

sudo apt-get install python-pip

sudo pip install flask

Nous indiquerons aussi que le langage Python est préinstallé et qu’il est tout de même conseillé de l’avoir déjà un peu utilisé. Pour les programmeurs du Raspberry Pi, c’est le langage essentiel, en particulier pour développer des scripts utilisant les broches GPIO et du matériel comme des capteurs de température, de mouvement, ou autres.

Dans mon cas nous voyons que l’utilitaire pip, un gestionnaire de paquets pour Python, ainsi que Flask sont déjà installés :

 
Sélectionnez
pi@raspberrypi:~ $ sudo apt-get install python-pip
Reading package lists... Done
Building dependency tree
Reading state information... Done
python-pip is already the newest version (18.1-5+rpt1).
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
pi@raspberrypi:~ $ sudo pip install flask
Looking in indexes: 
  https://pypi.org/simple, https://www.piwheels.org/simple
Requirement already satisfied: 
  flask in /usr/lib/python2.7/dist-packages (1.0.2)
pi@raspberrypi:~ $

Avec sudo pip3 install flask, nous pourrions faire la même vérification et constater que Flask est aussi installé pour la version 3 de Python.

III. Première utilisation de Flask

Nous allons commencer par un HTTP GET, tout simple, sans paramètre et ensuite avec du code pour y exécuter des fonctions GPIO.

Nous supposerons que le lecteur à suffisamment de connaissance avec Linux et nous commencerons donc par créer un répertoire de travail pour nos scripts Python dédié à Flask :

pi@raspberrypi:~ $ mkdir flaskeur

pi@raspberrypi:~ $ cd flaskeur

pi@raspberrypi:~/flaskeur


Nous allons créer un premier script Flask nommé salut_flaskeur.py comme ceci :

salut_flaskeur.py
Sélectionnez
# salut_flaskeur.py
from flask import Flask
app = Flask(__name_₎

@app.route('/')
def home():
   return "Salut Flaskeur!"
if __name__ == '__main__':
   app.run(host='0.0.0.0', port=5000, debug=True)

La fonction home() est définie par le mot clé def. Elle va simplement nous retourner le texte non formaté « Salut Flaskeur » et ceci pour le document racine de notre serveur Web qui est défini avec @app.route('/').

Si nous définissions un @app.route('/salut'), nous devrions alors entrer, depuis par exemple un PC Windows, http://192.168.1.143:5000/salut (voir ci-dessous) et un http://192.168.1.143:5000 entre autres nous retournerait un Not Found. Le code d’erreur 404, qui correspond à une page non trouvée, sera présent dans la fenêtre de notre explorateur Internet et dans la console Putty de notre Raspberry Pi.

Les autres instructions indiquent qu’une application Flask a démarré avec une entrée main() classique de Python. Le port par défaut de Flask est 5000. Il est tout à fait possible d’utiliser le port standard 80, voire d’autres comme 8000 ou 8080 qu’on rencontre souvent lors de l’utilisation de plusieurs sites Web sur la même machine. Je ne mentionnerai pas ici le protocole HTTPS où il faudrait utiliser from OpenSSL import SSL.

Nous utiliserons l’hôte host=0.0.0.0 afin de configurer le serveur Web Flask pour être visible au travers du réseau. Les sites de présentation de Flask pour le Pi utilisent souvent un simple app.run(debug=True)qui nécessiterait de travailler en mode écran avec clavier et souris, ou en mode VNC. Dans ce cas uniquement, un accès http://127.0.0.1:5000 sur la propre machine serait possible, donc avec par exemple le navigateur chromium-browser préinstallé sur le Pi 4, navigateur que je n’ai jamais utilisé.

Attention à l’indentation correcte (sur deux des lignes ici où j’ai choisi trois espaces) qui est nécessaire pour le langage Python. PyDev sous Eclipse, que nous allons aborder rapidement, aidera beaucoup en pouvant forcer l’utilisation de tabulateurs.

Travaillant moi-même sous Linux bien avant l’arrivée de l’éditeur GNU nano en l’an 2000, je suis plus habitué à utiliser l’éditeur vi. Mais, dans une console, nous pouvons très bien travailler avec l’éditeur nano, qui est traditionnellement utilisé, et avec la commande :

nano salut_flaskeur.py

Un Ctrl-C depuis Windows du code ci-dessus, suivi d’un Ctlr-O ou/et Ctrl-X, seront nécessaires pour sauver le code et quitter. La commande

cat salut_flaskeur.py

nous permettra de vérifier que le contenu est correct avant d’exécuter :

python3 salut_flaskeur.py

qui nous donnera ceci :

 
Sélectionnez
pi@raspberrypi:~/flaskeur $ python3 salut_flaskeur.py
 * Serving Flask app "flaskeur" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 108-533-113

Pour interrompre le serveur, un Ctrl-C sera nécessaire.

Nous indiquerons à nouveau que nous utilisons pour cet article et sur le Raspberry Pi la version 3 de Python, identifiable avec la commande python3 (python2, équivalent à python, serait pour la version 2) :

pi@raspberrypi:~/flaskeur $ python3 -V

Python 3.7.3

Pour démarrer le serveur Web Flask en arrière-plan et identifier qu’il est effectivement actif avec la commande ps qui montre les processus actifs :

 
Sélectionnez
pi@raspberrypi:~/flaskeur $ python3 salut_flaskeur.py &
[1] 2211
pi@raspberrypi:~/flaskeur $  * Serving Flask app "flaskeur" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 108-533-113
ps
  PID TTY          TIME CMD
  711 pts/0    00:00:00 bash
 2211 pts/0    00:00:00 python
 2213 pts/0    00:00:00 python
 2216 pts/0    00:00:00 ps

La commande :

sudo kill -9 2211 2213

nous permettra de stopper les deux processus concernés, c’est-à-dire le serveur Web.

III-A. Démarrage automatique par le Raspberry Pi

Au lancement du Raspberry Pi, il est possible d’indiquer que notre Web serveur Flask se lancera automatiquement, sans besoin d’une console pour le démarrer. Il est évidemment possible d’y ajouter d’autres serveurs Flask, donc avec des ports différents, voire d’autres applications, dans ce procédé de lancement automatique.

Notre salut_flaskeur.py est plus que primitif et il est à espérer que le lecteur s’attaquera à la suite de cet article.

Avec la commande :

sudo nano /etc/rc.local

nous ajouterons tout à la fin du fichier le lancement en arrière-plan d’httpserveur5000.sh :

 
Sélectionnez
……………………
 if [ "$_IP" ]; then
   printf "My IP address is %s\n" "$_IP"
 fi
 /home/pi/flaskeur/httpserveur5000.sh &
 exit 0

Nous devrons créer le script bash httpserveur5000.sh avec :

pi@raspberrypi:~/flaskeur $ nano httpserveur5000.sh

et contenant ici les deux commandes :

cd /home/pi/flaskeur

python3 salut_flaskeur.py

Le cd n’est pas ici nécessaire, mais plus tard, nous utiliserons dans nos scripts Flask d’autres ressources venant de ce répertoire. Nous mettrons aussi les droits correctement, testerons le script bash, l’interromprons avec Ctrl-C, avant de redémarrer le Pi avec un traditionnel sudo reboot :

chmod +x httpserveur5000.sh

/home/pi/flaskeur/httpserveur5000.sh

III-B. Premier test depuis notre PC

Nous utiliserons Chrome, Firefox ou notre navigateur Internet préféré depuis notre PC avec l’adresse http://192.168.1.143:5000/. Nous verrons alors apparaître le texte dans la fenêtre du navigateur Web :

Salut Flaskeur

Voilà ! C’est un bon début et cela fonctionne. Nous pourrions déjà ici modifier le port 5000, voir le texte ou encore définir un autre port et un autre fichier salut2_flaskeur.py. Les deux serveurs Web pourraient être lancés simultanément, en parallèle, nous permettant de les avoir actifs en même temps, par exemple pour des fonctionnalités différentes.

IV. PyDev sous Windows Eclipse

Les lecteurs qui ont lu mon autre article, PyDev, un IDE pour Python, sous Eclipse et pour le Raspberry Pi 3, pourront configurer leur Eclipse sur leur PC Windows… ou autre. C’est vraiment un plaisir avec le débogueur Python !

Lors de l’écriture de cet article, c’est seulement en y écrivant sa dernière partie que je me suis rendu compte qu’il était incontournable. En révisant la partie sur les Templates qui suit, j’ai tout de suite intégré le code dans mon projet Eclipse.

Les scripts Flask sont typiquement testés avec des URL qui ressemblent à quelque chose comme :

http://127.0.0.1/system

que nous verrons ci-dessous. Le fichier Flask sera ici system.py qui sera déposé dans un projet Python sous Eclipse, unique, pourquoi pas, pour tous les scripts Python de cet article. Afin de pouvoir activer depuis Eclipse l’URL de test, nous ajouterons une ligne commentée avec le caractère # :

# http://127.0.0.1/system

et ceci tout au début du fichier, sur la première ligne. Cela nous permettra avec le pointeur de la souris et en pressant la touche Ctrl, de nous voir présenter un menu où nous pourrons activer cette page Web, et ceci dans une fenêtre d’Eclipse, à droite de nos autres fichiers en édition sous Eclipse. C’est tout simplement géant !

Sous Eclipse et Windows, comme indiqué dans l’article référencé ci-dessus, il est possible de développer en Python avec différentes versions. Pour Flask, comme indiqué au début, j’ai choisi la version 3. Sous Eclipse il est possible de définir plusieurs configurations. Il sera plus facile de choisir la même version 3 que celle définie dans le PATH de Windows. Au premier test de notre fichier Python Flask sous Eclipse, nous pourrions remarquer que Flask manque et une installation pip pour Python sous Windows serait requise :

 
Sélectionnez
C:\Users\jb>python -m pip install flask
Collecting flask
Downloading https://files.pythonhosted.org/packages/
…

V. Flask Templates

Le terme Template ici est utilisé pour indiquer que nos scripts Python Flask vont être définis pour pouvoir présenter de jolies pages écrites en HTML contenant des parties variables.

Nous allons tout d’abord écrire un script Python Flask pour présenter l’information générale sur notre Raspberry Pi. Pour commencer, il faut créer un sous-répertoire qui contiendra le fichier html utilisé par cette technologie de Templates :

 
Sélectionnez
pi@raspberrypi:~ $ pwd
/home/pi
pi@raspberrypi:~ $ cd flaskeur/
pi@raspberrypi:~/flaskeur $ mkdir templates

Moi-même j’aime bien travailler sur mon PC Windows. J’utilise l’éditeur Notepad + + (référence : https://notepad-plus-plus.org/), vraiment cool et je l’associe à WinSCP et PuTTY. WinSCP est un outil pour Windows qui permet de transférer des fichiers entre un PC et un Raspberry Pi, voire l’inverse pour des sauvegardes (référence : https://winscp.net/eng/docs/lang:fr) alors que PuTTY nous permet de travailler depuis le PC, comme si étions dans une console bash du Raspberry Pi (référence : https://www.putty.org/).

Pour cet article, j’ai défini un répertoire de travail D:\RaspberryPi4\PythonFlask sur mon PC Windows. Ce répertoire est automatiquement ouvert dans WinSCP, car défini une fois dans sa configuration.

Avec sa jolie syntaxique pour Python et d’autres langages, je préfère Notepad + + sur le PC à nano sur le Raspberry Pi (rappel : je travaille sans écran ni clavier branchés sur le Pi, ni avec un client VNC). Cette manière de faire me permet de conserver facilement une sauvegarde de mon code. Pour de petites corrections sur le Raspberry Pi, j’utilise de préférence vi que je maîtrise, sans même y réfléchir, depuis plus de 30 ans. Je ne dois pas oublier de repasser le code sur le PC avec WinSCP, en sélectionnant tous les fichiers et le sous-répertoire flaskeur, les dates me montrant d’ailleurs si les fichiers sont identiques.

Dans le répertoire flaskeur du Raspberry Pi, nous allons donc écrire un fichier system.py contenant :

system.py
Sélectionnez
#Test avec "http://127.0.0.1:5001/system"

from flask import Flask, render_template
import platform
app = Flask(__name__)
 
@app.route("/system")
def hello():
  uname = platform.uname()
  thisdict = {
    "title": "Flask script system.py",
    "hello": "Hello Fred"
  }
  thisdict.update(uname._asdict())

  return render_template('system.html', templateData = thisdict)
 
if __name__ == "__main__":
  app.run(host='0.0.0.0', port=5001, debug=True

Et dans le sous-répertoire templates nous déposerons le fichier system.html :

system.html
Sélectionnez
<!DOCTYPE html>
   <head>
      <title>Sans titre pour l'instant</title>
   </head>

   <body>
      <table>
        {% for key, value in templateData.items() %}
          <tr>
            <td>{{key}}</td>
            <td>{{value}}</td>
          </tr>
        {% endfor %}
      </table>
   </body>
</html>

On se référera à la documentation de Flask et des Templates pour les détails et les constructions telles que les {{}}.

La méthode render_template() va extraire les valeurs du dictionnaire pour les intégrer dans la page Web qui sera générée au travers d’une fonction Jinja2, le moteur de template de Flask. Nous mentionnerons tout de même la page Template Designer Documentation à l’adresse https://jinja.palletsprojects.com/en/2.11.x/templates/.

Le code qui précède, à l'exception sans doute de sa syntaxe, est relativement simple et peut être directement vérifié sur le Raspberry Pi. Par contre, si nous désirions l’étendre, avec du code Python, il serait évidemment recommandé de le développer sur un PC Windows avec PyDev. Le bouton Run sous Eclipse va démarrer le serveur Web Flask en montrant la console d’exécution tout en bas, si configuré ainsi. La première ligne de commentaire contient la référence URL http://127.0.0.1:5001/system à utiliser et à entrer dans un explorateur Web. Avec 127.0.0.1, c’est-à-dire localhost, nous restons sur le PC pour tester notre script Flask. Nous répéterons, encore une fois ici, qu'Eclipse permet de naviguer avec la souris sur les mots http ou system, avec la touche Ctrl enfoncée, afin de se voir présenter un menu permettant d'activer le navigateur Web interne à Eclipse, et voir le résultat dans une nouvelle fenêtre intégrée ! Avec PyDev, nous ne pourrons pas utiliser le répertoire de travail D:\RaspberryPi4\PythonFlask sur notre PC Windows, car il sera intégré à un répertoire projet de notre espace de travail Eclipse (c’est expliqué dans l’article PyDev, un IDE pour Python, sous Eclipse et pour le Raspberry Pi 3 (mars 2019)).

Pour démarrer ce serveur Web Flask, c’est-à-dire system.py, sur le Raspberry Pi dans le répertoire flaskeur (un & additionnel, pour un démarrage en arrière-plan, est possible évidemment) il nous faudra un :

python3 system.py

En exécutant sur un PC Windows par exemple avec notre explorateur favori :

http://192.168.1.143:5001/system

Nous recevrons ceci sur deux colonnes :

 
Sélectionnez
title   Flask script system.py
hello   Hello Fred
system  Linux
node    raspberrypi
release 4.19.50-v7l+
version #895 SMP Thu Jun 20 16:03:42 BST 2019
machine armv7l
processor

La variable associée avec la clé processor n’est pas présente. Nous pourrions le vérifier sous Python 3 dans une console du Raspberry Pi. Le lecteur pourra s’amuser à introduire d’autres valeurs dans le dictionnaire pour render_template() passé à system.html et les extraire séparément pour les formater différemment en HTML, avec, par exemple, des bords, voire du css.

VI. Schéma Fritzing – Relais et Buzzer

En connectant juste un relais et un buzzer alarme sur notre Raspberry Pi, c’est suffisant pour les tests de nos prochaines applications Flask. Nous verrons qu’il est aussi très facile d’accéder en Python à des composants GPIO à partir de scripts Flask.

Nous trouverons le relais (par exemple COM-KY019RM) et le buzzer piézoélectrique passif (par exemple KY-006) sur les sites d’achat de matériel dédié à l’Arduino ou au Raspberry Pi. Les deux composants sont construits pour pouvoir être attachés directement aux broches GPIO du Raspberry Pi.

Le schéma Fritzing présenté ici est dédié au Raspberry Pi. Nous noterons que les broches des Raspberry Pi 3 ou Pi 4 sont identiques.

Image non disponible
Relais et Buzzer

La numérotation des pins se trouve sur de nombreux sites comme

https://pi4j.com/1.2/pins/model-3b-plus-rev1.html que j’utilise moi-même pour la programmation du GPIO en Java. J’ai la bonne habitude de toujours indiquer le numéro de la broche physique dans mon code, c’est-à-dire correspondant au mode GPIO.BOARD (voir ci-dessous). Si l’on prend le fil jaune pour le contrôle du relais, c’est la huitième broche physique à droite, numérotée paire, donc la broche numéro 16.

En utilisant deux broches de terre différentes (GND), nous n’aurons pas de platine d’essai. Le buzzer, le relais et le Raspberry Pi ayant des broches mâles, il suffit de se procurer des fils avec deux connecteurs femelles. J’utilise volontiers des fils de 20 cm qui me permettent, en particulier pour le buzzer, de placer mes composants assez éloignés du boîtier contenant le Raspberry Pi. Je n’indique pas ici comme connecter le relais à la partie haute tension, procédure qui nécessitera les précautions nécessaires, voire une vérification par un électricien.

VII. Déclencher un relais sur le Raspberry Pi

Avant de pouvoir visionner l’état d’un relais avec Flask, nous allons commencer par un petit exercice avec ce composant électrique (5/220 Volt) que nous allons enclencher ou déclencher toutes les quatre secondes. Ce logiciel sera extérieur au serveur Flask Web et aussi écrit en langage Python (fichier relayonoff.py déposé dans le répertoire flaskeur de notre Raspberry Pi) :

relayonoff.py
Sélectionnez
# coding: utf-8
import RPi.GPIO as GPIO
import time

RelayPin = 16

def setup():
  GPIO.setwarnings(False)
  GPIO.setmode(GPIO.BOARD)         # Numéro GPIO par broche physique
  GPIO.setup(RelayPin, GPIO.OUT)   # RelayPin en mode output

  try:
    while True:
      GPIO.output(RelayPin, GPIO.HIGH)
      state = GPIO.input(RelayPin)
      print(state)
      time.sleep(4)

      GPIO.output(RelayPin, GPIO.LOW)

      state = GPIO.input(RelayPin)
      print(state)
      time.sleep(4)
  except KeyboardInterrupt:  # Interruption avec 'Ctrl+C'
     GPIO.cleanup()          # Ressources libérées

if __name__ == '__main__':   # Démarrage en Python
  setup()

Le script Python va ouvrir et fermer le relais, chaque 4 s.
La méthode GPIO.input() nous permet d’identifier si le relais est enclenché (1) ou non (0). Ces 1 et 0 viendront continuellement sur la console.

Le script Python Flask relaystate.py se présentera ainsi :

relaystate.py
Sélectionnez
from flask import Flask, render_template
import datetime
import RPi.GPIO as GPIO
app = Flask(__name__)

RelayPin = 16

GPIO.setmode(GPIO.BOARD)         # Numero GPIO par broche physique
GPIO.setup(RelayPin, GPIO.OUT)   # RelayPin en mode output

@app.route("/relaystate")
def hello():
   state = "ON"
   if GPIO.input(RelayPin) == 0:
      state = "OFF"
   templateData = {
      'title' : 'Relay state!',
      'relaystate': state
      }
   return render_template('relaystate.html', **templateData)

if __name__ == "__main__":
   app.run(host='0.0.0.0', port=80, debug=True)

Avec son template relaystate.html dans le sous-répertoire templates :

relaystate.html
Sélectionnez
<!DOCTYPE html>
   <head>
      <title>{{ title }}</title>
   </head>

   <body>
      <h3>Relay state: {{ relaystate }}</h3>
   </body>
</html>

C’est incroyablement simple, presque trop simple, il faut le reconnaître. La méthode render_template() va extraire la valeur de la variable relaystate pour l’intégrer dans la page Web générée au travers de la fonction Jinja2, le moteur de template de Flask.

Suivant l’état du relais, nous aurons une réponse pour http://192.168.1.143/relaystate tel que :

Image non disponible

Le lecteur pourrait s’amuser à écrire un formulaire qui fait basculer l’état du relais sur clic du bouton de soumission et avec la page qui est rafraîchie. L’état pourrait être sauvegardé dans une base de données SQLite, que nous allons traiter plus loin.

Pour pouvoir visionner cette page, il nous faudra les deux commandes :

 
Sélectionnez
python3 relayonoff.py &
sudo python3 relaystate.py &

Nous noterons ici, qu’à chaque lancement de programme avec &, un message apparaît avec le numéro du process entre crochets. Il est possible de l’utiliser pour stopper le process avec la commande sudo kill -9.

Pour relayonoff.py, le kill ne permettra pas de libérer le port GPIO proprement avec GPIO.cleanup().
Nous pourrions alors avoir au prochain démarrage un

 
Sélectionnez
relayonoff.py:9: RuntimeWarning: This channel is already in use, continuing anyway.  
Use GPIO.setwarnings(False) to disable warnings.
GPIO.setup(RelayPin, GPIO.OUT)   # RelayPin en mode output

qui indique que la ressource est utilisée. Le script fonctionnera quand même et le lecteur pourrait même rajouter un

 
Sélectionnez
GPIO.setwarnings(False)

au début dedef setup()pour empêcher cet avertissement.

Avant de continuer la lecture de cet article ou de modifier la présentation de cette page livrée par le serveur Flask Web, il nous faudra stopper les deux processus concernés. Une commande ps nous montrera les trois processus à stopper éventuellement avec un sudo kill ‑9.

En jouant avec cet exercice, nous remarquerons que l’état du relais sera perdu si nous terminions le processus relayonoff.py, ou pire encore, que nous éteignions notre Raspberry Pi.

Notre relaystate.py pourrait aussi contenir une entrée @app.route("/reboot") ou nous pourrions alors relancer notre Raspberry Pi du Web :

 
Sélectionnez
@app.route("/reboot")
def reboot(): 
    #os.system('sudo reboot') fonctionne aussi
    os.system('sudo shutdown -r now')

    return ''

Nous pourrions alors conserver l’état du relais dans un fichier, par exemple

 
Sélectionnez
f = open("relaystate.txt", "w")
f.write("ON")
f.close()

Ici, si le relais est enclenché. L’interruption avec 'Ctrl+C' du processus relayonoff.py, activera l’entrée except KeyboardInterrupt où ce code pourrait aussi être introduit.

L’état du relais pourrait alors être récupéré du fichier relaystate.txt au redémarrage du Pi et évidemment si l’application nécessite de conserver cet état. Nous avons vu au chapitre III ADémarrage automatique par le Raspberry Pi, comment démarrer un processus au démarrage du Raspberry Pi.

VIII. Un exemple Flask avec GET d’un ESP32 et des accès GPIO

Avant de passer à des exemples contenant des paramètres GET, nous commencerons par un exercice impliquant un ESP32 qui va détecter le passage d’un chat et envoyer l’événement sur un Raspberry Pi.

La séquence complète est ainsi définie :

  • l’ESP32 détecte un mouvement avec un capteur PIR ;
  • il envoie une requête /beep au serveur Web Flask ;
  • ce dernier génère un son sur le buzzer du Raspberry Pi ;
  • une pause de 10 s sur l’ESP32 permettra de se déplacer à la fenêtre, sans bruit, pour identifier le passage éventuel d’un chat ;
  • un son variable ultrasonique répulsif est généré sur le buzzer de l’ESP32.

Nous commencerons par le script Flask nommé beepalarm.py, sur le Raspberry Pi, correspondant à notre GET désiré :

beepalarm.py
Sélectionnez
from threading import Lock
from flask import Flask
import time
import RPi.GPIO as GPIO
lock = Lock()
 
app = Flask(__name__)
@app.route("/beep")
def beep():  
   with lock:
      GPIO.setmode(GPIO.BOARD)
      GPIO.setup(12, GPIO.OUT)
      p = GPIO.PWM(12, 500)
      p.start(0)
      for dc in range(0, 101, 5):
         p.ChangeDutyCycle(dc/2)
         p.ChangeFrequency(50+(2*dc))
         time.sleep(0.15)
      p.stop()
      GPIO.cleanup()
 return ''
 if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8002, debug=True)

Ce script n’a pas de template et c’est un simple GET du protocole HTTP sans paramètre et sur le port 8002.

Le lock est équivalent à un synchronized en Java ! Le code inclus dans le bloc with lock sera bloqué jusqu’au GPIO.cleanup(). Sans ce blocage plusieurs requêtes Web simultanées à cette adresse produiraient une erreur, car cette ressource GPIO n’aurait pas été libérée. C’est facile à tester avec deux fenêtres de navigateur Web pour la même adresse.

Le PWM (Pulse Width Modulation, en français Modulation de largeur d’impulsion) permet de générer différents types de sons. Pour nos besoins, nous nous sommes contentés de jouer avec le Duty Cycle (le rapport cyclique) et la fréquence.

Le fichier script beepalarm.py sera déposé dans le répertoire flaskeur et démarré avec :

pi@raspberrypi:~/flaskeur $ python3 beepalarm.py

Un sudo n’est pas nécessaire pour le port 8002. Un test simple se fera avec

http://192.168.1.143:8002/beep

dans un navigateur Web extérieur au Raspberry Pi, et où aucun retour visible ne sera présenté.

Qu’est-ce qui n’est pas terrible terrible ici ! Le /beep ne reçoit aucun paramètre. Aucun moyen d’indiquer par exemple la génération d’un son différent ou d’une longueur variable.

Nous allons maintenant présenter la partie ESP. C’est un ESP32-WROOM-32, mais un ESP8266 meilleur marché irait aussi comme un Arduino avec WiFi.

Le schéma Fritzing correspondant au sketch esp32buzzerchat.ino ci-dessous se présente ainsi :

IX. Schéma Fritzing – ESP32 – PIR et Buzzer

Image non disponible
ESP32 - PIR et Buzzer


Le terme sketch est utilisé pour indiquer que c’est du code C/C + + développé et compilé avec l’IDE de l’Arduino. Le type de carte pour l’IDE sera l’ ESP32 Dev Module.
Le sketch esp32buzzerchat.ino qui suit fera le travail :

esp32buzzerchat.ino
Sélectionnez
#include <WiFi.h>
#include <HTTPClient.h>

//13.5KHZ-19.5KHZ : Efficace pour repousser les animaux comme les souris, les rats, les chiens, les renards, les martres (belettes), etc.
//19.5KHZ-24.5KHZ : Efficace pour repousser les animaux tels que les chats, les ratons laveurs, les blaireaux, les mouffettes, etc.

int freq = 2000;
int channel = 0;
int pinPir = 13;
int resolution = 8;
int pinBuzzer = 12;

int httpCode = 1000;

const char* ssid = "....";
const char* pwd  = "....";
  
void setup() {
  Serial.begin(9600);

  Serial.print("Connexion à ");
  Serial.println(ssid);
  WiFi.begin(ssid, pwd);
  while (WiFi.status() != WL_CONNECTED) {
    delay(200);
    Serial.print(".");
  }
  //Montre l'adresse IP
  Serial.println("");
  Serial.println("WiFi connecté!");
  Serial.println("Adresse IP: ");
  Serial.println(WiFi.localIP());
  
  ledcSetup(channel, freq, resolution);
  ledcAttachPin(pinBuzzer, channel);

  pinMode(pinPir,INPUT);
}
  
void loop() { 
  bool isDetected = digitalRead(pinPir);
 
  if (isDetected){
    Serial.println("Présence détectée");

    HTTPClient http;
    http.begin("http://192.168.1.143:8002/beep"); 
    http.addHeader("Content-Type", "text/plain");
    httpCode = http.GET();
    Serial.print("http return: ");
    Serial.println(httpCode);
    http.end();  //free resources

    delay(10000);
    Serial.println("Start beep");
    ledcWrite(channel, 255);

    int freq = 17000;  //Hertz
    while (freq < 24000) {
       ledcWriteTone(channel, freq);
       freq += 100;
       delay(80);
    }
    while (freq > 17000) {
       ledcWriteTone(channel, freq);
       freq -= 100;
       delay(60);
    }

    ledcWriteTone(channel, 0);
    Serial.println("End of beep"); 
    
    delay(100);    
  }
 
  delay(100);
}

Le script Flask beep.py décrit ci-dessus devra être actif sur le Raspberry Pi afin de recevoir l’événement.

La génération de fréquences est similaire à celle décrite précédemment pour le GPIO.PWM()dans le script Flask beepalarm.py précédent. Les fréquences utilisées sont décrites dans le sketch esp32buzzerchat.ino ci-dessus. Les spectres des sons émis par le buzzer ont été vérifiés avec l’application Android Spectroid et correspondent à des répulsifs de chat qu’on trouve sur le marché.

Comme déjà indiqué en début d’article, je ne donnerai que peu de détails pour les ESP32 et ESP8266. Le code devrait aussi suffire sans trop d’explications. Nous voyons qu’une connexion au routeur est nécessaire pour obtenir l’adresse IP de l’ESP32. Il faudra évidemment indiquer le ssid et le mot de passe (pwd) du WiFi et connaître l’adresse IP du Raspberry Pi où se trouve notre serveur Web Flask. C'est le digitalRead(pinPir) qui nous indiquera la présence d'un mouvement. Le lecteur pourra évidemment changer les sons émis avec de jolies séquences non « ultrasoniques ».

X. Notre beep avec un get paramétré

Le script Python Flask précédent nommé beepalarm.py correspondait à un GET sans paramètre.

Les lecteurs ont sans doute déjà rencontré la forme qui suit avec le ? et un paramètre suivi d’une valeur :

http://192.168.1.143:8080/beepalarm2?delay=10

Cette forme de GET est plus pratique qu’un POST pour mettre en place un protocole entre Raspberry Pi, ESP ou autres systèmes embarqués. Dans le script esp32buzzerchat.ino que nous venons de présenter, nous avions un simple /beep. Nous aurions pu lui ajouter un ou plusieurs paramètres pour indiquer différentes émissions de son avec le buzzer du Raspberry Pi. Ce serait aussi un moyen d’y passer par exemple des indications de températures mesurées.

Dans l’exemple qui suit, nous allons faire du tout simple, c’est-à-dire le même son que notre /beep, mais avec, cette fois-ci, un simple délai avant de l’envoyer :

beepalarm2.py
Sélectionnez
from threading import Lock
from flask import Flask, request
import time
import os
import RPi.GPIO as GPIO

lock = Lock()

app = Flask(__name__)

def asound():
  GPIO.setmode(GPIO.BOARD)
  GPIO.setup(12, GPIO.OUT)

  p = GPIO.PWM(12, 500)
  p.start(0)

  for dc in range(0, 101, 5):
    p.ChangeDutyCycle(dc/2)
    p.ChangeFrequency(50+(2*dc))
    time.sleep(0.15)

  p.stop()
  GPIO.cleanup()
  return

@app.route("/beep")
def beep():
   with lock:
     asound()
     return 'Beep done'

@app.route("/beepdelay")
def beepdelay():
   with lock:
     delay = request.args.get('delay')
     try:
       secs = int(delay)
     except TypeError:
       secs = 0

     time.sleep(secs)
     asound()
     return 'Beep done with a ' + delay + ' seconds delay'

if __name__ == "__main__":
   app.run(host='0.0.0.0', port=8002, debug=True)

Le request.args.get('delay') nous permet de récupérer la valeur passée avec le
/beepalarm2?delay=10.

XI. GET en Python depuis un PC Windows

Le code qui suit a été inclus dans le script pcget1.py sur notre PC Windows. C’est un exemple tout simple d’un GET en Python qu’on retrouvera facilement sur le Web. Il pourra être personnalisé à souhait et est utilisable sur d’autres plateformes, voire sur un autre Raspberry Pi. La bibliothèque Python à rechercher sera urllib.

pcget1.py
Sélectionnez
import urllib.request
url = "http://192.168.1.143:8080/rebootd?delay=2"
request = urllib.request.Request(url)

try:
    response = urllib.request.urlopen(request)
    data_content = response.read()
    print(data_content.decode('utf-8'))
except:
    print("http://192.168.1.143:8080 not reached or error")

Le 192.168.1.143:8080 et le rebootd sont les références à notre script précédent reboot2.py qui devra être lancé sur notre Raspberry Pi. J’ai longtemps buté sur le print(data_content) qui me retournait un b’ (octet littéral) devant la réponse. En ajoutant decode('utf-8'), j’ai été satisfait !

XII. GET en Java depuis un PC Windows avec Eclipse

Le code qui suit fonctionne aussi. La classe LectureUrl est reprise telle quelle de mon livre, UN LIVRE SUR JAVA, PYTHON, ECLIPSE ET LE RASPBERRY PI 3 avec l’URL modifiée. Je ne l’ai pas installée ou essayée sur un Raspberry Pi, mais exécutée directement depuis Eclipse sur mon PC Windows.

LectureUrl.java
Sélectionnez
import java.net.*;
import java.io.*;

public class LectureUrl {
  public static void main(String[] args) throws Exception {
    URL notreUrl = 
      new URL("http://192.168.1.143:8080/rebootd?delay=2");
    BufferedReader in = new BufferedReader(new 
    InputStreamReader(notreUrl.openStream()));
    String inputLine;
    while ((inputLine = in.readLine()) != null)
      System.out.println(inputLine);
      in.close();
    }
}

Dans la console d’Eclipse, j’ai reçu un reboot with delay of 2 secs et après un délai de deux secondes. J’ai évidemment lancé préalablement python reboot2.py dans le répertoire flaskeur de mon Raspberry Pi avec les sudo reboot en commentaire par simplicité.

XIII. SQLite, Flask et température

Nous avons à présent tout le matériel Flask nécessaire pour envoyer depuis l’extérieur, une température par exemple, et de la stocker sur un Raspberry Pi dans une base de données SQLite. Cette dernière est un choix naturel vu sa simplicité.

Dans une seconde partie, nous y ajouterons des exemples de code pour envoyer ces températures avec différents langages, comme Python, C/C + + ou Java, et qui dépendent de la plateforme où se situe le capteur de température.

XIII-A. Création d’une base de données et d’une table

Le site de référence SQLite se trouve ici : https://www.sqlite.org/index.html.

Dans mon livre, UN LIVRE SUR JAVA, PYTHON, ECLIPSE ET LE RASPBERRY PI 3, un chapitre entier est dédié à SQLite pour le langage Java. Je ne peux évidemment pas faire de même ici, voire encore plus pour les débutants en SQL. Repris de Wikipédia : SQL pour sigle de Structured Query Language (en français langage de requête structurée) est un langage informatique normalisé servant à exploiter des bases de données relationnelles.

Je vais utiliser ici la même stratégie, c’est-à-dire l’explorateur SQLite DB Browser à installer sur un PC Windows. Nous pourrons alors mettre en place notre base de données, qui est un fichier, définir la ou les tables nécessaires, et copier ce fichier sur le Raspberry Pi, avec WinSCP, dans le répertoire où nous aurons le code Python, Flask ou non, voire d’autres langages, pour jouer avec nos températures.

La définition de la table et de ses champs va dépendre de l’usage qu’on en fera et aussi de se faciliter la tâche pour les enregistrements et ensuite les récupérer pour les convertir dans différents formats. Comme nous utiliserons principalement Python, une attention particulière sera nécessaire pour les données concernant les dates et les heures. Il faudra aussi savoir par exemple si une précision à la seconde est suffisante, ce qui en principe n’est pas nécessaire pour une température, et si le client nous passe cette information. Il y a plein d’alternatives et de choix, comme de ne pas définir la date et l’heure côté client et de laisser le Raspberry Pi y mettre les valeurs lors de la réception d’un événement sous forme de GET.

Le script Python sqlitecreate1.py qui suit nous montre la création d’une table events qui nous permettra de stocker nos enregistrements d’événements :

sqlitecreate1.py
Sélectionnez
import sqlite3
from datetime import datetime

sqliteConnection = sqlite3.connect('SQLite_Python.db')
cursor = sqliteConnection.cursor()

sqlite_create_table_query = \
  '''CREATE TABLE events (nameevent TEXT NOT NULL, \
  namesource TEXT NOT NULL, value REAL NOT NULL, \
  thedate TEXT NOT NULL, thetime TEXT NOT NULL);'''
cursor.execute(sqlite_create_table_query)

now = datetime.now()
da = now.strftime("%d/%m/%Y")
ti = now.strftime("%H:%M:%S")

sqlite_insert_with_param = \
  """INSERT INTO 'events'('nameevent', 'namesource', \
  'value', 'thedate', 'thetime') VALUES (?, ?, ?, ?, ?);"""

data_tuple = ('temperature', 'esp32a', 23.1, da, ti)
cursor.execute(sqlite_insert_with_param, data_tuple)
sqliteConnection.commit()

Ce script a été développé avec Notepad + +, testé sur un PC dans une console CMD et ceci dans le répertoire de nos scripts Flask qui viennent parfois du Raspberry Pi avec WinSCP.

J’aime bien le DB Browser SQLite (son site officiel est https://sqlitebrowser.org/). On pourra le télécharger et l’installer sur son PC et suivant son système d’exploitation. Il nous permettrait de créer à la main une nouvelle base de données, de nouvelles tables, de les adapter ou de les corriger. La consultation, la correction ou l’effacement des données est vraiment simple :

Image non disponible
Structure de la base de données
Image non disponible
Contenu avec un enregistrement de test

Si nous exécutons une seconde fois ce script sqlitecreate1.py, il ne fonctionnera pas, car la base de données – c’est-à-dire le fichier SQLite_Python.db – existera déjà. Il faudrait effacer le fichier ou tester sa présence, voire demander d’effacer toutes les données. L’INSERT INTO est juste là pour vérifier un enregistrement. On pourra effacer l’entrée avec le DB Browser SQLite avant de le déposer sur le Pi pour être utilisé dans nos scripts Flask.

Nous avons choisi de séparer la date de l’heure de la journée. Cela pourra simplifier notre logiciel en particulier pour récupérer des données d’une journée particulière. Le type TEXTE est un choix, nous aurions pu utiliser REAL ou INTEGER, ce dernier nous limiterait aux secondes, suffisant ici quand même.

Avec l’onglet Parcourir les données, il est possible de cliquer sur une valeur spécifique de notre base de données et de la modifier. Il faudra alors cliquer sur le bouton Appliquer. Ce sera pratique plus tard si nous voulions tester d’autres scripts Flask et vérifier notre code avec des valeurs négatives pratiquement impossibles à générer dans notre chambre à coucher !

Si nous désirions utiliser un système de gestion de bases de données comme MySQL, en lieu et place de SQLite, il faudrait rechercher sur les sites dédiés à Python et Flask, et nous y découvririons du code comme :

 
Sélectionnez
import mysql.connector

mydb = mysql.connector.connect(
  host="localhost",
  user="utilisateur",
  passwd="mot de passe"
)

Pour localhost, ce serait sur le Raspberry Pi (ou un PC) et une installation de MySQL serait nécessaire.

SQLite n’a pas besoin d’installation, puisqu’il fonctionne avec un simple fichier. Au contraire de SQLite, MySQL peut évidemment se trouver sur une autre machine, comme un NAS (un Serveur de stockage en réseau), où localhost serait remplacé par une adresse IP configurée et atteignable.

XIV. SQLite intégration dans Flask

Nous allons passer à présent sur le Raspberry Pi. Notre fichier SQLite, SQLite_Python.db, préparé ci-dessus sur notre PC, sera transféré sur notre Pi avec WinScp et dans notre répertoire de travail /home/pi/flaskeur. Le logiciel sqlite3 devrait être déjà installé sur notre Raspberry Pi. Nous pouvons le vérifier avec :

 
Sélectionnez
pi@raspberrypi:~/flaskeur ~ $ sqlite3 -version

Dans mon cas, j’ai reçu un, 3.27.2 2019-02-25 16:06:06, indiquant la date de la version de sqlite3. Si ce n’est pas installé, nous le ferons avec :

 
Sélectionnez
pi@raspberrypi:~/flaskeur ~ $ sudo apt-get update
pi@raspberrypi:~/flaskeur ~ $ sudo apt-getupgrade
pi@raspberrypi:~/flaskeur $ sudo apt-get install sqlite3

qui inclut une mise à niveau de Raspbian. Il faudra entrer un Y majuscule lors de la requête finale pour l’installation).

La commande sqlite3 qui suit nous fait entrer dans un mode console où les commandes SQLite sont disponibles :

 
Sélectionnez
pi@raspberrypi:~/flaskeur $ sqlite3 SQLite_Python.db
SQLite version 3.27.2 2019-02-25 16:06:06
Enter « .help » for usage hints.
sqlite> .tables
events
sqlite> .schema events
CREATE TABLE events (nameevent TEXT NOT NULL, namesource TEXT NOT NULL, value REAL NOT NULL, thedate TEXT NOT NULL, thetime TEXT NOT NULL);
sqlite> select * from events;
temperature|esp32a|23.1|31/10/2019|18:58:49
sqlite>
pi@raspberrypi:~/flaskeur $

Les commandes .tables et .schema nous permettent de découvrir respectivement la seule table présente dans notre base de données et sa structure.

La commande select nous permet d’obtenir le contenu des données enregistrées. Il n’y a ici qu’un seul enregistrement : un événement température de 23.1 reçu d’un ESP32 en octobre 2019 en fin de journée !

De nombreux sites de présentation de SQLite ou de tutoriels sont disponibles sur le Web, par exemple : https://www.sqlitetutorial.net/.

Il est pratique de définir dans le répertoire de travail du Raspberry Pi un certain nombre de scripts bash pour nous aider, par exemple selectall.sh qu’on aura rendu exécutable avec la commande chmod +x selectall.sh préalablement. Il vous montrera le contenu de notre base de données dans une console du Pi :

 
TéléchargerSélectionnez
pi@raspberrypi:~/flaskeur $ cat selectall.sh
sqlite3 SQLite_Python.db <<EOF
select * from events
EOF

Un script comme deleteall.sh nous permettrait si nécessaire d’effacer tous les enregistrements de la base de données utilisée dans cet article :

 
Sélectionnez
pi@raspberrypi:~/flaskeur $ cat deleteall.sh
sqlite3 SQLite_Python.db <<EOF
delete from events
EOF

XV. Événement : GET en Python

De la même manière que notre script pcget1.py précédant, nous allons passer au Raspberry Pi un événement température généré depuis un PC Windows. Le code serait évidemment identique, ou pratiquement, sur un autre système supportant le langage Python, comme un autre Raspberry Pi !

Nous l’avons nommé pcget2.py :

pcget2.py
Sélectionnez
import urllib.request
url = "http://192.168.1.143:8080/event?temperature=23.4"
request = urllib.request.Request(url)
response = urllib.request.urlopen(request)
data_content = response.read()
print(data_content.decode('utf-8'))

L’entrée /event est un choix, comme le paramètre temperature. Le 23.4 est une valeur de test, puisque nous n’avons pas de capteur sur notre PC.

Nous avons défini la date et l’heure dans notre table SQLite et choisirons ici de laisser le serveur Flask responsable de générer ces deux valeurs.

Nous pourrions nous imaginer des clients, qui collectionnent des données, bien avant de les transférer en paquet dans un serveur Flask. Dans ce cas, la date et l’heure de l’événement seraient nécessaires.

Le http://192.168.1.143:8080/event?temperature=23.4 pourrait être remplacé par http://192.168.1.143:8080/event?temperature=23.4&namesource=PCnamesource est le champ indiquant la source de l’événement, ici un PC.

Nous verrons comment le serveur Flask pourra remplacer cette valeur, si elle n’est pas spécifiée, par l’adresse IP du client, ce qui semble plus intelligent que le nom PC qu’il faudrait corriger par une valeur plus explicite.

Le decode('utf-8') est nécessaire, car data_content est une série d’octets (type bytes) entourée d’un b'' à l’affichage, et qu’il faut convertir en string.

XVI. SQLite : Instruction INSERT

Nous avons à présent tout le matériel, le langage Python, son framework Flask et une base de données SQLite, afin de récupérer une température reçue d’une méthode GET de requête HTTP et de la stocker.

Pour être honnête, le code Python Flask qui suit, nommé getevent1.py, a été intégralement programmé et testé sur mon PC Windows avec Notepad + +, et en parallèle exécuté dans une console CMD, et tout ceci dans mon répertoire de travail D:\RaspberryPi4\PythonFlask. J’ai pu ainsi formater proprement le code pour sa présentation ici, dans cet article. Il est possible de le faire directement sur le Raspberry Pi, mais pour les débutants, avec peu de connaissances SQL, voire de Python, un développement sous PyDev est vivement à conseiller.

getevent1.py
Sélectionnez
from flask import Flask, request
import os
import time
import sqlite3
from datetime import datetime

app = Flask(__name__)

@app.route('/event')
def event():
   temperature = request.args.get('temperature')
   # print (temperature)
   namesource = request.args.get('namesource')
   # print (namesource)
   ip_adress = request.remote_addr
   # print(ip_adress)

   if namesource is None: # Test if namesource is None
     namesource = ip_adress

   try:
     ftemperature = float(temperature)
   except TypeError:
     ftempera = 0

   sqliteConnection = sqlite3.connect('SQLite_Python.db')
   cursor = sqliteConnection.cursor()

   now = datetime.now()
   da = now.strftime("%d/%m/%Y")
   ti = now.strftime("%H:%M:%S")

   sqlite_insert_with_param = """INSERT INTO 'events'('nameevent',\
                              'namesource', 'value', 'thedate', \
                              'thetime') VALUES (?, ?, ?, ?, ?);"""

   data_tuple = ('temperature', namesource, ftemperature, da, ti)
   cursor.execute(sqlite_insert_with_param, data_tuple)
   sqliteConnection.commit()

   return 'Event temperature received with value of ' + \
          str(ftemperature) + ' degrees'

if __name__ == "__main__":
   app.run(host='0.0.0.0', port=8080, debug=True)

Nous commencerons évidemment par une référence sur le Web comme SQLite Python avec plus spécifiquement SQLite Python: Inserting Data

un très bon tutoriel SQLite où nous trouverons le matériel indispensable pour programmer en SQL sous Python et Flask ! Ce tutoriel sera aussi une aide pour comprendre certaines constructions utilisées dans le code qui précède.

Dans ce script getevent1.py vraiment simplifié, il n’y a pratiquement aucun traitement d’erreurs. Lorsque j’ai eu terminé les tests, j’ai ajouté des # pour commenter les lignes print () en Python.

Par contre, si le paramètre namesource n’est pas passé dans le GET par le client, et ce n’est pas une mauvaise idée du tout, il est remplacé par l’adresse IP du client, disponible par le protocole de la requête.

Le gros avantage de tester sous Windows Notepad + + ou PyDev, est le fait que nous pouvons garder en permanence notre DB Browser for SQLite ouvert sur notre PC Windows, donc du fichier SQLite_Python.db, afin de vérifier les données enregistrées dans la base de données. Notre DB Browser a aussi une option d’export dans le menu Fichier suivi d’Export, où nous avons plusieurs choix comme le format SQL, CSV ou JSON. Avec le format SQL nous pourrions récupérer les instructions SQL complètes de création de la table events et de son contenu. Nous pourrions y découvrir par exemple :

INSERT INTO events (nameevent,namesource,value,thedate,thetime) VALUES (‘temperature’,’192.168.1.110′,23.4,’24/11/2019′,’18:54:11′);

Cet INSERT en SQL pourrait être adapté et copié dans la fenêtre d’entrée de l’onglet Exécuter SQL et enregistré dans la base de données en utilisant le mini icône play. C’est très pratique pendant le développement de nos scripts Python pour vérifier notre code, ici cursor.execute(sqlite_insert_with_param, data_tuple), pour en faire les corrections et adaptations si nécessaire.

Le format CSV (Comma-separated values), est un format texte représentant des données tabulaires avec des valeurs séparées par des virgules. Tout en fin d’article nous parlerons d’idée d’extensions, où ce format serait utilisable, par exemple avec Excel, pour générer plus tard des graphiques d’événements ou de températures.

Avec l’onglet Parcourir les données, il est aussi facile d’effacer facilement un ou plusieurs (Ctrl au clavier) enregistrements d’une base de données qui risque de grossir inutilement pendant ces premiers tests.

Sous Windows PC nous associerons évidemment l'extension .db à notre DB Browswer for SQLite. Cela nous permettra d'ouvrir cet outil de navigation de notre base de données, aussi bien depuis l'explorateur de fichiers de Windows, que depuis la liste des fichiers visibles dans le Project Explorer d'Eclipse.

Si nous sommes sur le Raspberry Pi, nous avons déjà montré comment visionner les enregistrements avec selectall.sh.

XVII. Les passages de personnes dans SQLite

Précédemment, avec notre ESP32 équipé d’un PIR (détecteur de mouvements), c’était notre sketch esp32buzzerchat.ino, nous avions informé le Raspberry Pi, avec un GET du protocole HTTP, du passage de chats à l’extérieur de notre maison. C’est évident que nous allons arrêter d’émettre des ultrasons qui commencent à nous casser les oreilles.

Nous allons déposer l’ESP32 précédemment décrit, dans un endroit où nous aimerions compter le nombre de passages de personnes, cette fois-ci, et avec le code du sketch esp32passage.ino suivant :

esp32passage.ino
Sélectionnez
#include <WiFi.h>
#include <HTTPClient.h>

int pinPir = 13;
int resolution = 8;

int httpCode = 200;

const char* ssid = "....";
const char* pwd  = "....";
  
void setup() {
  Serial.begin(9600);

  Serial.print("Connexion à ");
  Serial.println(ssid);
  WiFi.begin(ssid, pwd);
  while (WiFi.status() != WL_CONNECTED) {
    delay(200);
    Serial.print(".");
  }
  //Montre l'adresse IP
  Serial.println("");
  Serial.println("WiFi connecté!");
  Serial.println("Adresse IP: ");
  Serial.println(WiFi.localIP());

  pinMode(pinPir,INPUT);
}
  
void loop() { 
  bool isDetected = digitalRead(pinPir);
 
  if (isDetected){
    Serial.println("Passage détecté");

    HTTPClient http;
    http.begin("http://192.168.1.143:8002/passage"); 
    http.addHeader("Content-Type", "text/plain");
    httpCode = http.GET();
    Serial.print("http return: ");
    Serial.println(httpCode);
    http.end();  //free resources

    delay(5000);
  }
 
  delay(100);
}

Il faudra à nouveau ici indiquer le ssid et le mot de passe (pwd) du WiFi. Les nombreux print() vont nous aider évidemment dans la phase de développement. C’est à nouveau le digitalRead(pinPir) qui nous indiquera la présence d’un mouvement afin de générer le GET sur le Raspberry Pi.

Nous avons utilisé le port 8002 avec cet exemple sur l’ESP32 pour des questions pratiques : nous avons séparé cette partie dédiée à la détection de mouvements des autres consacrées à la température. Mais la base de données est la même, si nous la laissons dans le même répertoire.

Le choix d’une pause de 5 s pourrait permettre d’ignorer le passage d’une même personne plusieurs fois durant cette période, afin de limiter le nombre d’enregistrements.

Nous allons donc modifier le script Flask précédent (getevent1.py) pour recevoir l’événement /passage et le stocker pour statistique dans la table events de notre base de données SQLite_Python.db. Oh, comme c’est simple maintenant ce script getevent2.py avec le port 8002 :

getevent2.py
Sélectionnez
from flask import Flask, request
import os
import time
import sqlite3
from datetime import datetime

app = Flask(__name__)

@app.route('/passage')
def passage():
   ip_adress = request.remote_addr

   sqliteConnection = sqlite3.connect('SQLite_Python.db')
   cursor = sqliteConnection.cursor()

   now = datetime.now()
   da = now.strftime("%d/%m/%Y")
   ti = now.strftime("%H:%M:%S")

   sqlite_insert_with_param = """INSERT INTO 'events'('nameevent',\
                              'namesource', 'value', 'thedate', \
                              'thetime') VALUES (?, ?, ?, ?, ?);"""

   data_tuple = ('movement', ip_adress, 0, da, ti)
   cursor.execute(sqlite_insert_with_param, data_tuple)
   sqliteConnection.commit()

   return 'Event movement received'

if __name__ == "__main__":
   app.run(host='0.0.0.0', port=8002, debug=True)

Si nous avons gardé notre base de données SQLite, sans effacer les données de température, nous recevrons avec ./selectall.sh, décrit précédemment, toutes les entrées enregistrées depuis le début, donc de sa création.

Avec un SQL select sur un champ défini et une valeur spécifique comme celle-ci:

 
Sélectionnez
pi@raspberrypi:~/flaskeur $ sqlite3 SQLite_Python.db
SQLite version 3.27.2 2019-02-25 16:06:06
Enter « .help » for usage hints.
sqlite> select theTime from events where nameevent = ‘movement’;
17:20:42
17:24:07
17:25:16
…….

Nous aurions alors les heures précises des mouvements détectés. C’est ici que nous pourrions à nouveau passer un peu de temps avec le tutoriel SQLite : https://www.sqlitetutorial.net/.

XVIII. Collectionner les températures depuis un ESP8266

Le schéma Fritzing présenté ici est pour un NodeMCU qui est équivalent à un ESP8266MOD DOIT.AM que j’ai moi-même utilisé.

Image non disponible
ESP8266 et un DS18B20

Le Dallas DS18B20 peut être alimenté en mode parasite, c’est-à-dire avec à la fois les broches marquées + et – sur la terre, où comme ici en mode normal. Je n’expliquerai pas en détail ces modes qu’on peut retrouver sur le Web. Il en va de même avec le D2, où l’on retrouvera de nombreuses références indiquant qu’il correspond au GPIO 4 indiqué dans le script ci-dessous.

Le code, développé avec l’IDE de l’Arduino et le type de carte Generic ESP8266 Module se présentera ainsi (sketch esp8266tempflask.ino) :

esp8266tempflask.ino
Sélectionnez
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266HTTPClient.h>

#include <OneWire.h>
#include <DallasTemperature.h>

const char* ssid     = "....";
const char* password = "....";

// GPIO du DS18B20
const int oneWireBus = 4;

int httpCode = 200;

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(oneWireBus);

// Pass our oneWire reference to Dallas Temperature sensor 
DallasTemperature sensors(&oneWire);

void setup() {
  // Start the Serial Monitor
  Serial.begin(115200);
  Serial.println("");
  Serial.println("esp8266tempflask started");
  
  // Start the DS18B20 sensor
  sensors.begin();

  WiFi.begin(ssid, password);
  Serial.println("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  sensors.requestTemperatures(); 
  float temperatureC = sensors.getTempCByIndex(0);
  Serial.print(temperatureC);
  Serial.println("ºC");

  char buffer[5];
  String tempfl = dtostrf(temperatureC, 4, 1, buffer);
  tempfl.trim();
  Serial.println("http://192.168.1.143:8080/event?temperature=" + tempfl);

  HTTPClient http;
  http.begin("http://192.168.1.143:8080/event?temperature=" + tempfl); 
 
  http.addHeader("Content-Type", "text/plain");
  httpCode = http.GET();
  Serial.print("http return: ");
  Serial.println(httpCode);
  http.end();  //free resources

  int minutes = 5;
  delay(1000*60*minutes);
}

Nous avons gardé le port 8080 et nous pourrons réutiliser évidemment le script précédent getevent1.py sur le Raspberry Pi en indiquant à nouveau le ssid et le mot de passe (pwd) du WiFi. En déconnectant l’ESP8266 et le réinstallant dans un endroit différent, nous constaterons que cela fonctionne correctement.

En éditant le fichier /etc/rc.local sur le Raspberry Pi, nous pourrions y ajouter un script shell afin de démarrer le script Python getevent1.py en arrière-plan et dans le répertoire où se situe le script et la base de données. De cette manière, chaque fois que nous démarrerons le Raspberry Pi, les données de température collectionnées sur divers ESP8266 ou ESP32, seront stockées dans SQLite.

Quand nous aurons suffisamment d’enregistrements, nous pourrions transférer le fichier SQLite_Python.db sur notre PC pour développer le code qui va suivre, un peu plus évolué, pour extraire des données en SQL et envoyer un courriel avec des indications sur les températures mesurées.

Idée d’extension : écrire un petit serveur Web sur l’ESP8266 pour modifier ces cinq minutes, l’adresse IP et le port du serveur Flask, et redémarrer esp8266tempflask.ino.

Dans cet article, nous avons utilisé d’autres ports que le 8080, afin de séparer les fonctionnalités. Le port 8002 a été utilisé pour détecter des mouvements depuis un ESP32. Comme la base de données est utilisable pour plusieurs sortes d’événements, il serait possible d’utiliser le même port en faisant les corrections nécessaires, et par exemple coder l’entrée /passage dans un script Flask unique (voir le sketch Arduino esp32passage.ino et le script Python getevent2.py).

XIX. Envoi par courriel des températures extrêmes d’une journée

Cette dernière partie va extraire de la base de données SQLite les températures d’une journée, depuis minuit, et reçues de notre dernier ESP8266. Nous allons « calculer » les deux températures extrêmes, le minimum et le maximum, pour les présenter dans une page Web toute simple avec un bouton optionnel pour envoyer un courriel.

Pour tester cette partie, je l’ai faite entièrement sous Eclipse depuis un PC Windows pour me faciliter la tâche et après avoir repris avec WinSCP mon fichier SQLite_Python.db déjà bien peuplé à la suite de mes tests.

Il est tout à fait possible de développer cette partie sous Windows avec Notepad + + et une console CMD, voire directement sur le Raspberry Pi. Mais si on désire l’étendre un peu, y ajouter du code Python ou SQLite, cela deviendrait vite un casse-tête. De plus le DB Browser for SQLite sous Windows nous permettrait de vérifier des fonctions SQL comme SELECT min(value) FROM events et ceci en utilisant l’onglet Exécuter le SQL et sa fenêtre, avant de le coder en Python.

Le code, raisonnablement simplifié, sans trop de fioritures Flask et Python, se trouve dans le fichier min_max_sqlite_email_get.py :

min_max_sqlite_email_get.py
Sélectionnez
#Test with "http://127.0.0.1:8080"

from flask import Flask, request, render_template
from datetime import datetime
import sqlite3
import smtplib, ssl

app = Flask(__name__)

def getMinMax():
    now = datetime.now()
    da = now.strftime("%d/%m/%Y")  #Today
    
    sqliteConnection = sqlite3.connect('SQLite_Python.db')
    cursor = sqliteConnection.cursor()
    cursor.execute("SELECT min(value) FROM events \
where nameevent = 'temperature' AND thedate = '" + da +"'") 
    rows1 = cursor.fetchall()

    cursor.execute("SELECT max(value) FROM events \
where nameevent = 'temperature' AND thedate = '" + da +"'") 
    rows2 = cursor.fetchall()
        
    return rows1[0][0], rows2[0][0], da

@app.route("/")
def hello():     
    lemin, lemax, ladate = getMinMax() # Assign tuple

    templateData = {
      'lemin' : lemin,
      'lemax' : lemax,
      'ladate': ladate
    }
    
    return render_template('mailform.html', **templateData)

@app.route("/sendmail")
def sendmail():
    port = 587  # For starttls
    smtp_server = "smtp.gmail.com"
    sender_email = "...."
    password = "...."
    
    receiver_email = request.args.get('emailadd') 
    if len(receiver_email) == 0:
      return "No email address"
    if receiver_email.find("@") == -1:
      return "Not an email address"
      
    da = request.args.get('ladate')
    mint = request.args.get('mint')
    maxt = request.args.get('maxt') 

    message = """\
Subject: Temperatures minimum and maximum for the """ + str(da) + """\

Minimum """ + str(mint) + """\
\nMaximum """ + str(maxt) + """\
\nThis message is sent from Flask Python min_max_sqlite_email_get.py"""

    context = ssl.create_default_context()
    with smtplib.SMTP(smtp_server, port) as server:
      server.starttls(context=context)
      server.login(sender_email, password)
      server.sendmail(sender_email, receiver_email, message)
    
    return "Email to " + request.args.get('emailadd', '') + ' with ' + request.args.get('mint', '') + ' and ' + request.args.get('maxt', '')

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8080)


Et dans le sous-répertoire templates, nous déposerons évidemment le fichier mailform.html :

mailform.html
Sélectionnez
<!DOCTYPE html>
   <head>
      <title>Temperatures: minimum and maximum</title>
   </head>

   <body>
      <H3>Minimum and maximum: {{lemin}} and {{lemax}} </H3>
      
      <H4>For date: {{ladate}}
      <P>Maybe for sending to:
      <form action="/sendmail" method="GET"><input name="emailadd">
        <input type="submit" value="Email address">
        <input type="hidden" name="ladate" value="{{ladate}}">
        <input type="hidden" name="mint" value="{{lemin}}">
        <input type="hidden" name="maxt" value="{{lemax}}">
      </form></H4>
   </body>
</html>

Un des cœurs du code Python est évidemment la fonction getMinMax() retournant un Tuple avec les températures minimale et maximale de la journée. C'est pour la journée au moment de la demande. S'il est 14:00, nous recevrons donc les deux températures depuis minuit. Si nous voulions d'autres extrêmes, pour par exemple une journée entière ou sur 24 heures, le code devrait être sérieusement étendu. Ici on fait presque dans le plus simple possible. Ce qui est intéressant ici ce sont le méthodes min(value) et max(value), appelées souvent méthodes d’agrégation, qui nous retournent les extrêmes d'une colonne d'une requête où nous avons déjà sélectionné un type d’événement particulier et une date fixe. La date est gardée pour être plus tard passée au document html du script Flask.

Les deux select ne contiennent aucune référence à la source de la température. Il faudrait y ajouter un where sur le champ namesource. Ce serait le cas si nous désirions sélectionner la pièce où la température est mesurée. Le code se compliquerait alors.

Vu la complexité du code et les possibilités d'extension, c'est un peu difficile de s'imaginer de développer cette application Flask sans un outil élaboré, par exemple avec PyDev sous Eclipse.

Pour l'envoi d'un courriel, il faudra identifier les informations du serveur SMTP, il faudra connaître ces informations. J’ai utilisé moi-même mon compte Gmail avec le port 587 (starttls). Il est possible de les retrouver, par exemple pour les utilisateurs de Thunderbird, sous Outil / Paramètres des comptes et tout en bas Serveur sortant SMTP.

Je ne donnerai pas plus de détails, car de nombreux sites Web nous expliquent comment générer des emails en Python, voire y joindre des attachements (le mot clé à utiliser lors des recherches sera MIMEMultipart).

Grâce à PyDEv sous Eclipse, après un Run avec le bouton, toujours en utilisant le pointeur de la souris sur la première ligne du code Python contenant http://127.0.0.1:8080, et en pressant la touche Ctrl, la page Web se présenterait ainsi :

Image non disponible

L'onglet min_max_sqlite_email_get contient le code de notre script Python Flask avec la fameuse ligne commentée Test with "http://127.0.0.1:8080" nous permettant d'examiner le résultat, comme expliqué précédemment.

Si nous n'avons pas de température pour ce jour, nous verrions alors deux None à la place de ces deux températures. Lors du développement sur le PC, nous pourrions alors, avec notre DB Browswer for SQLite, corriger les dates, voire y ajouter de nouvelles entrées de température.

Le code reste simple, et nous pourrions y ajouter plus de cas d'erreurs et ne pas envoyer de courriel dans ces cas. Le bouton Email address nous permettra d'envoyer un courriel basique à l'adresse indiquée où le code ne vérifie que les cas où le champ est vide ou ne contient pas de caractère @.

Après avoir programmé et testé min_max_sqlite_email_get.py sur un PC Windows nous pourrions le transférer avec WinSCP sur le Raspberry Pi, dans le répertoire flaskeur, afin de l'exécuter avec python3 min_max_sqlite_email_get.py.

Un test depuis le PC avec http://192.168.1.143:8080/ dans notre explorateur Web favori, si notre Raspberry Pi possède cette adresse IP, évidemment.

XX. Et la suite…

Plein d'exercices, voire des applications imposantes sont possibles, car il y a ici tellement de sujets abordés dans cet article.

Comme nous collectionnons, dans une base de données, des températures ou des passages d’animaux, désirés ou non dans une pièce, nous pourrions alors nous pencher sur des statistiques, des moyennes, et pourquoi pas la génération de graphiques sur une page Web, voire un document envoyé par courriel et généré par Excel.

Des idées de cours d’introduction au langage Python, au Raspberry Pi, aux ESP32 ou ESP8266 sont des sujets qui me viendraient à l'esprit en terminant cet article, qui m'a pris un peu plus de deux mois, à vrai dire, mais m'a apporté beaucoup de satisfaction ! J'avais évidemment déjà beaucoup de matériel, et beaucoup de code réutilisable, sauf la partie Flask elle-même, vraiment nouvelle pour moi.

XXI. Remerciements

Un grand merci à f-leb pour son énorme travail de relecture, sa précision dans les détails et ses commentaires judicieux qui ont été considérés.

Un grand merci également à ClaudeLELOUP pour son travail minutieux de correction orthographique et grammaticale.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Licence Creative Commons
Le contenu de cet article est rédigé par Jean-Bernard Boichat et est mis à disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2013 Developpez.com.