Menu Fermer

Python Web Flask et SQLite pour le Raspberry Pi

Date de la dernière modification: 18 janvier 2023

Cet article a été soumis à la rédaction de developpez.com et a été publié en mars 2020. De nombreuses adaptations et suggestions des correcteurs y ont été apportées. Il est aussi disponible au format PDF et avec un nouveau titre: Python Flask et SQLite pour le Raspberry Pi 3 ou 4 – exemples de communication avec des ESP32 ou ESP8266.

En poursuivant sur ce même sujet, il m’est venu à l’idée d’intégrer ce serveur Flask sur un NAS (Network Attached Storage) de Synology. L’article en travail se trouve ici même: Python Web Flask sur un NAS (Synology)   

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++. Etant 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 documenter 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 LEDs 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érieurs, 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 :

Raspberry Pi 3 B

Quelques thèmes couverts sont (les titres ci-dessous sont des liens encrés dans ce document :)

Installation de Python Flask sur le Raspberry Pi 3 ou 4
Première utilisation de Flask
Démarrage automatique
Premiers tests
PyDev sous Windows Eclipse
Templates
Exemple avec Relais et Buzzer (Fritzing)

GET d’un ESP32 et des accès GPIO
PIR et Buzzer (Fritzing ESP32)
Pi Reboot avec Flask
Get depuis un PC Windows (Python, Java)
SQLITE, Flask et température
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 sur le site developpez.net, en particulier dans les groupes d’intérêt dédiés au Raspberry Pi et à l’Arduino (ESP32 et ESP8266). J’y ai d’ailleurs déjà publié les articles:
MicroPython pour le NodeMCU (ESP8266) avec Thonny (août 2019)
PyDev, un IDE pour Python, sous Eclipse et pour le Raspberry Pi 3 (mars 2019) 

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 Wikipedia:

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 assumerons 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 cette article, je ne donnerai aussi que peu d’explications sur les broches et leur 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.

Comme j’utilise mes Raspberry Pi sans écran ni clavier, toutes les procédures d’installation et de vérification sont exécutées dans un console du système d’exploration, 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 faites 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.

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:

Installation de Flask

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

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-getupgrade

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 :

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:~ $

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 assumerons 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
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 nous retournerait un Not Found comme tous autres URL autre ici qu’un /salut. Le code d’erreur 404, qui correspond à une page non trouvée, sera présente dans la fenêtre de notre explorateur Internet et dans la console Putty de notre Raspberry Pi.

Les autres instructions indiquent que c’est une application Flask a démarrer avec une entrée main() classique de Python. Le port par défaut de Flask est 5000. Il est tout à fait possible. Je ne mentionnerai pas ici le protocole HTTPS où il faudrait utiliser from OpenSSL import SSL.

Nous utiliserons le 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 utilise 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 ou j’ai choisis 3 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 :

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 ici, 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 :

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.

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. 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 le lancement en arrière plan d’httpserveur5000.sh F:

……………………
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 script Flask d’autres ressource 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

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 pourrait ê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.

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 detest, nous ajouterons une ligne commenté 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 indiquer dans l’article référencé ci-dessus, il est possible de développer en Python avec différentes versions. Pour Flask, comme indiquer 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 requit:

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

Flask Templates

Le terme Template ici est utilisé pour indiquer que nos script Python Flask vont être définis une fonctionnalité de Flask permettant de 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 utiliser par cette technologie de Templates :

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 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, ni avec un client VNC). Cette manière de faire me permet conserver facilement une sauvegarde de mon code. Pour de petite 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 :


#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:


<!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 {{}}.

Le code qui précède est relativement simple, à l’exception sans doute de sa syntaxe, et il 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 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 server 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::

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 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 render_template() passé à system.html et les extraire séparément pour les formater différemment en HTML avec, par exemple, des bord voire du css.

Schéma Fritzing – Relais et Buzzer

En déposant 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.

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.

La numérotation des pins se trouvent sur de nombreux site 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 le 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.

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 GPIO électrique (5/220 Volt) que nous allons enclencher ou déclencher chaque 4 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) :


# coding: utf-8
import RPi.GPIO as GPIO
import time

RelayPin = 16    # broche physique 16 

def setup():
  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)
      f = open("relaystate.txt", "w")
      f.write("ON")
      f.close()
      time.sleep(4)

      GPIO.output(RelayPin, GPIO.LOW)

      f = open("relaystate.txt", "w")
      f.write("OFF")
      f.close()

      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 10 secs, et stocker l’état dans un fichier nommer relaystate.txt. Le contenu de ce dernier n’a rien de plus que 2 ou 3 caractères et cela va simplifier la génération de la page HTML. Pour connaître l’état du relais, il n’est pas possible d’ouvrir cette broche GPIO en GPIO.IN. C’est la raison d’utiliser ici un fichier qui est un mécanisme simple.

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

from flask import Flask, render_template
import datetime
app = Flask(__name__)

@app.route("/relaystate")
def hello():
   file = open("relaystate.txt", "r") 
   state = file.readline()
   
   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 :

<!DOCTYPE html>
   <head>
      <title>{{ title }}</title>
   </head>

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

C’est incroyablement simple, presque trop simple, il faut le reconnaître. Suivant l’état du relais nous aurons une réponse pour http://192.168.1.143/relaystate tel que :

Relay state: ON

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ée dans une base de données SQLite, que nous allons traiter plus loin.

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

 python3 relayonoff.py &
 sudo python3 relaystate.py &

Nous noterons ici qu’à chaque lancement de processus avec &, un message apparaît avec le numéro du process entre crochet. Il est possible de l’utiliser pour la commande sudo kill -9. Pour relayonoff.py, le kill ne va pas exécuter GPIO.cleanup(). Nous pourrions alors avoir au prochain démarrage un

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 rajouter un

GPIO.setwarnings(False) 

au début de def setup().

Avant de continuer la lecture 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 3 processus à stopper éventuellement avec un sudo kill -9.

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

Avant de passer à des exemple 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 secs 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é :

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 protocol HTTP sans paramètre et sur le port 8002.

Le lock est équivalent à un synchonized 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 la ressource GPIO du GPIO ne serait pas unique. 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 contenter de jouer avec le Duty Cycle (cycle de service) 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 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.

Schéma Fritzing – ESP32 – PIR et Buzzer

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

Le terme sketch est utilisé pour indiqué 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 :

#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 (belette) 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 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 correspondant à 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 “ultrasonic”.

Un exemple Flask de reboot du Raspberry Pi

Nous restons dans le même contexte d’un GET traditionnel avec le script que j’ai nommé reboot.py:

from flask import Flask
import os
app = Flask(__name__)

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

return ''

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

Il est possible de choisir un autre port ou d’intégrer ce /reboot dans un script plus complexe contenant d’autres entrées. Un serveur Web séparé sur 8080 et démarré en parallèle avec d’autres serveurs ou applications est une solution que j’ai adoptée où un:

http://192.168.1.143:8080/reboot 

qui relancerait le Raspberry Pi.

Notre reboot avec un get paramétrisé

Les lecteurs ont sans doute déjà rencontré la forme:

http://192.168.1.143:8080/rebootd?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.

Ici nous allons permettre un reboot du Raspberry Pi après un délais d’un nombre de secondes passé par paramètre. J’ai intégré le script Flask correspondant dans le fichier reboot2.py :

from flask import Flask, request
import os
import time

app = Flask(__name__)

@app.route('/reboot')
def reboot():
   #os.system('sudo reboot') 
   return 'reboot'

@app.route('/rebootd')
def rebootd():
   delay = request.args.get('delay')
   print (delay)
   try:
     secs = int(delay)
   except TypeError:
     secs = 0

   time.sleep(secs)
   #os.system('sudo reboot')
   return 'reboot with delay of ' + str(secs) + ' secs'

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


J’ai évidemment lancé préalablement python3 reboot2.py dans le répertoire flaskeur de mon Raspberry Pi. J’avais utilisé à l’origine la version 2 de Python, et j’ai dû corrigé le print delay. C’est une des erreurs classiques. Il faut mettre les parenthèses.

Après avoir vérifié les /reboot, /rebootd et /rebootd?delay=5 (5 secondes par exemple), nous pourrons remettre des return ”, ôter les deux caractères # de commentaire et vérifier l’exécution réelle des reboot du Raspberry Pi.

Le message reboot with delay of 5 secs viendra évidemment 5 secs plus tard que la requête, si cette valeur de 5 est donnée.

Nous allons à présent montrer quelques exemples de GET exécutés de l’extérieur: 

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 plate-formes voire sur un autre Raspberry Pi. La librairie Python à rechercher sera urllib

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!

GET en Java depuis un PC Windows avec Eclipse

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

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 après un délais de 2 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é.

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 un second 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.

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 3un 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 Wikipedia: 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:

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 ici). On pourra le télécharger et l’installer sur son PC et suivant son système d’exploitation. Il nous permettrais 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:

Structure de la base de données
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 où 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 impossible à générer dans notre chambre à coucher!

Si l’on désirait utiliser 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:

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 serait nécessaire. SQLite n’a pas besoin d’installation, puisque c’est un simple fichier. mySQL peut évidemment se trouver sur une autre machine, comme un NAS (un Serveur de stockage en réseau), où localhost serait remplacer par une adresse IP configurée et atteignable.

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. Je l’ai fait sur Buster avec les commandes:

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

Ici, cela inclut la mise à jour de Buster (ou Raspbian si installé). Il faudra entrer un Y majuscule lors de la requête finale pour l’installation) Nous ferons la vérification avec:

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 du logiciel sqlite3. La commande sqlite3 qui suit nous fait entrer dans un mode console où les commandes SQLIte sont disponibles:

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, comme par exemple: https://www.sqlitetutorial.net/.

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

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:

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

Evénement: GET en Python

De la 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 évidement identique, ou pratiquement, sur un autre système supportant le langage Python, comme un autre Raspberry Pi!

Nous l’avons nommé pcget2.py:

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 serait nécessaire.

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=PC”namesource 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 de bytes à convertir. Si nous recevrions un string entouré d’un b”, une suite d’octets littérales.

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 nommé getevent1.py a été complètement 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 formaté proprement la 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 à conseiller vivement.

from flask import Flask, request
import os
import time
import sqlite3
from datetime import datetime

app = Flask(__name__)

@app.route('/event')
def event():
   tempera = request.args.get('temperature')
   # print (tempera)
   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:
     ftempera = float(tempera)
   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, ftempera, da, ti)
   cursor.execute(sqlite_insert_with_param, data_tuple)
   sqliteConnection.commit()

   return 'Event temperature received with value of ' + \
          str(ftempera) + ' 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ète 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 script 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 enregistrement avec selectall.sh.

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é, avec un GET du protocole HTTP, le Raspberry Pi du passage de chats à l’extérieur de notre maison. C’est évident que nous allons retirer qui émet des ultrasons et qui commence à 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:

#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 utiliser 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 secs 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ée SQLite_Python.db. Oh, comme c’est simple maintenant ce script getevent2.py avec le port 8002:

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:

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/.


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é.

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étails 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 on 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):

#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 garder 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’enregistrement, 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 5 minutes, l’adresse IP et le port du serveur Flask, et redémarrer esp8266tempflask.ino.

Dans cette 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écessaire, 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 ).

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ées, 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:

#Test with "http://127.0.0.1:8080"

from flask import Flask, request
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 

    return '<H3>Minimum and maximum: ' + str(lemin) + ' and ' + \
str(lemax) + ' degrees</H3><P>' \
'<H4>For date: ' + ladate + '<P>Maybe for sending send to:  \
<form action="/sendmail" method="GET"><input name="emailadd">' + \
'<input type="submit" value="Email address">' + \
'<input type="hidden" name="ladate" value="' + str(ladate) + '">' + \
'<input type="hidden" name="mint" value="' + str(lemin) + '">' + \
'<input type="hidden" name="maxt" value="' + str(lemax) + '">' + \
'</form></H4>'

@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)

Un des cœurs de ce code 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é souvent aggregate, 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 information du serveur SMTP il faudra connaître ces informations. Il est possible de les retrouver, par exemple pour les utilisateur de Thunderbird, sous Outil / Paramètres des comptes et tout en bas Serveur sortants SMTP.

Pour l’envoi d’un courriel, il faudra identifier les information du serveur SMTP, il faudra connaître ces informations. J’ai utiliser moi-même mon compte Gmail avec le port 587 (starttls). Il est possible de les retrouver, par exemple pour les utilisateur de Thunderbird, sous Outil / Paramètres des comptes et tout en bas Serveur sortants 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).

Un test depuis PyDev sous Eclipse se présenterait ainsi:

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 expliquer 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.

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.

Et la suite ….

Plein d’exercices sont possibles ici, il y a tellement de sujets abordés ici. Comme nous collectionnons, dans une base de données, des températures ou des passages de personnes ou d’animaux 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 2 mois à vrai dire et m’a apporter beaucoup de satisfaction! J’avais évidemment déjà tout le matériel, et beaucoup de code réutilisable, sauf la partie Flask elle-même, vraiment nouvelle pour moi.