Affichage des articles dont le libellé est ESP32. Afficher tous les articles
Affichage des articles dont le libellé est ESP32. Afficher tous les articles

dimanche 7 décembre 2025

PYTHON : Initiation

 


PYTHON : Initiation


Ce mois-ci je propose une petite initiation au langage Python. Le but ici n'est pas de produire un tutoriel exhaustif, déjà très nombreux sur le WEB, mais de donner envie d'utiliser ce langage moderne.

La première version de Python est sortie en 1991. La version 2 date de 2000, la version 3 de 2008. C'est à l'heure actuelle un des langages les plus utilisés, aussi bien sur PC que sur plateforme embarquée. Pour ma part, Python représente une grande part des lignes de code que j'ai écrites aussi bien dans ma vie privée que professionnelle.

Python peut être utilisé pour une grande variété de projets :

  • automatisation
  • projets scientifiques
  • serveurs WEB
  • bases de données
  • communication
  • interface graphique
  • etc.

Sur ce blog il suffit de rechercher le mot "python" pour constater que j'ai déjà publié pas mal de petits scripts.

1. Cibles

Les plateformes sur lesquelles peut tourner Python sont nombreuses :

  • machine Windows, MacOs, Linux
  • cartes SBC comme la Raspberry PI, BeagleBone, Banana PI

Mais il existe aussi des versions de Python légères, comme MicroPython, qui peuvent se contenter de plateformes moins puissantes comme les ESP32, STM32.

Le principal intérêt de Python sur des plateformes aussi variées est la portabilité et on comprend vite l'intérêt d'utiliser un langage unique : limiter la formation.

A l'heure actuelle je développe essentiellement avec :

  • sur PC : Python, HTML, JavaScript
  • sur RASPBERRY PI : Python, HTML, JavaScript
  • sur ESP8266, ESP32, ARDUINO : C, C++, HTML, JavaScript

2. Installation

L'installation de Python est simple.

Sous Linux, en fonction de la distribution, il est souvent pré-installé. Pour le vérifier, lancer la commande suivante :

python -V

S'il est installé il va afficher sa version :

Python 3.12.3

Si ce n'est pas le cas, il faut utiliser la méthode d'installation de la distribution :

Sous Ubuntu : sudo apt-get install python 

Sous Fedora : sudo dnf install python3

Sous Windows il faudra télécharger le logiciel d'installation ici.

Sous Windows n'oubliez pas de cocher la case "Add Python 3.XX to PATH" :

2.1. Installation de packages

Installer le langage Python ne veut pas dire que l'on installe simplement un interpréteur nu avec des instructions basiques. Un nombre très important de librairies sont présentes : The Python Standard Library

Par contre d'autres librairies peuvent vous apporter des fonctionnalités avancées. Par exemple, on peut très bien développer un serveur WEB à l'aide du package http.server, présent dans la librairie standard, mais c'est un peu comme travailler avec un marteau et une enclume. Pour développer des application plus cossues, on a souvent recours à des frameworks comme Jango, ou CherryPy.

2.2. Pip

Certains sites proposent l'installation de leurs packages à l'aide de pip (Package Installer for Python).

C'est en effet un procédé très simple, qui permet d'installer des packages sans avoir à télécharger une archive et se préoccuper de l'endroit où il va falloir la décompresser.

Sous Ubuntu ou Debian : sudo apt install python3-pip

Sous Windows : Comment installer PIP sur Windows

On pourra ainsi installer par exemple le package de communication série PySerial comme ceci :

pip install pyserial

3. La console

Python met à votre disposition une console qui vous permettra d'exécuter du code sans avoir à créer de fichier source.

Il suffit pour cela de taper la commande python dans un terminal :

python

ou (sous Windows)

py

Python 3.12.3 (main, Nov  6 2025, 13:44:16) [GCC 13.3.0] on linux

Type "help", "copyright", "credits" or "license" for more information.

>>>

Ensuite on entre le code à exécuter : 

print("Hello Python")
Hello Python
>>> 

On quitte la console en tapant CTRL-D.

Cette console est un moyen très pratique de taper quelques lignes de code rapidement pour faire des essais.

4. Un tutoriel ?

Il existe énormément de tutoriels sur le langage Python. Je vous propose le tutoriel officiel pour démarrer.

Celui de W3school, en anglais, aborde des sujets supplémentaires :

  • mathplotlib
  • fichiers
  • base de données MySQL
  • etc.

Attention : il est assez déconseillé d'adopter de vieux tutoriels Python2 pour travailler en Python3, car les différences sont assez importantes.

5. Quel éditeur ?

Pour écrire du code, on utilise un éditeur de texte. Certains éditeurs évolués comme Visual Studio Code vous apporteront un certain confort, en particulier la coloration syntaxique.

L'écriture de code en Python n'utilise pas d'accolades pour définir les blocs de code comme en C ou C++, mais une indentation.

En C, on utilise des accolades :

for (i=0 ; i < 4 ; i++)
{
    printf("blabla\n");
    printf("%d\n", i);
}

L'équivalent en Python :

for i in range(4):
    print("blabla")
    print(i)

Ce code produira le résultat suivant :

blabla
0
blabla
1
blabla
2
blabla
3

Mais si vous écrivez :

for i in range(4):
    print("blabla")
print(i)

Ce code produira le résultat suivant :

blabla
blabla
blabla
blabla
3

Attention donc.

Pour l'indentation il va falloir faire un choix. Soit vous utilisez la tabulation, soit vous utilisez des espaces. La majeure partie des éditeurs modernes permettent d'insérer des espaces lorsque l'on insère une tabulation. En général, la taille d'une tabulation est configurable. Un nombre de 3 ou 4 espaces est en général une bonne valeur.

6. Domaines d'application

Les domaines d'application sont très nombreux. Je n'ai pas l'intention de tous les énumérer, mais en voici quelques exemples :

6.1. Instructions de base

Quand on débute en Python, on constate assez vite que la simplicité est au rendez-vous. De plus, Python emprunte au langage C certaines caractéristiques essentielles, mais aussi à JavaScript :

Commentaire :

# comment

Commentaire sur plusieurs lignes :

'''
comment using string literals
with triple quotes
'''

Déclaration d'un entier :

a = 100

Déclaration d'un entier en notation hexadécimale :

a = 0x44

Déclaration d'un caractère en notation hexadécimale :

a = "\x44"

Déclaration d'une chaîne de caractères :

s = "this is a string"
s = 'this is a string'
a = "\x44\x33"

On peut accéder à un caractère particulier dans la chaîne, comme en C :

t = "string"
print(t[2])

Les tableaux :

t = [0, 1, 2, 3]
print(t[2])

t = (0, 1, 2, 3)
print(t[2])

On remarque deux types de tableaux : le premier, appelé liste, modifiable (entre crochets), le deuxième, appelé tuple, non modifiable (entre parenthèses).

Tous les tableaux sont indexés en partant de ZÉRO, comme en C.

En Python on peut ajouter, modifier ou supprimer un élément de liste :

t = [0, 1, 2, 3]
t.append(5)    # ajoute un élément dont la valeur est 5
t[4] = 12        # modifie l'élément 4
t.pop(2)         # retire l'élément 2

Au final, le contenu sera : [0, 1, 3, 12]

Appel de fonction ou de méthode :

function(arguments)
object.method(arguments)

Un dictionnaire, que l'on peut assimiler à une structure C :

x = {"name" : "John", "age" : 36}

Les habitués du C ou de JavaScript ne seront donc pas complètement perdus.

On remarque que le type des variables n'est pas explicitement déclaré.

On peut parfaitement écrire :

a = 100
a = "abcdef"

La variable a change donc de type à volonté. C'est un point qui m'a fortement perturbé à mes débuts. On m'a répondu "simple question d'ouverture d'esprit". J'ai donc arrêté de me plaindre et me suis accroché.

En fait chaque fonction est libre de contrôler ses arguments :

range("3")

Produit une exception :

TypeError: 'str' object cannot be interpreted as an integer

6.1.1. Boucle for

En C on écrirait :

for (i=0 ; i < 4 ; i++)
{
    printf("i a pour valeur %d\n", i);
}

L'équivalent en Python :

for i in range(4):
    print("i a pour valeur", i)

Dans cet exemple les différences sont flagrantes :

  • pas d'accolades mais une simple indentation (tabulations ou espaces)
  • pas de point-virgule

6.1.2. Séquence

On l'aura compris, la fonction range() retourne une séquence de nombres, 0 à 4 dans l'exemple précédent.

range() peut prendre 3 arguments :

range(3, 10, 2) # retourne une séquence de 3 to 9, par pas de deux : [3, 5, 7, 9]

Si on désire une séquence sans logique précise, on peut l'écrire comme ceci :

for i in [0, 2, 4, 8, 13]:
    print("i a pour valeur", i)

[0, 2, 4, 8, 13] est aussi une séquence. On remarque déjà qu'en C ou C++ il serait plus difficile d'écrire l'équivalent.

c = ["Un", "problème", "sans", "solution", "est", "un", "problème", "mal", "posé"]

for i in range(len(c)):
    print("i vaut", i, "et c[i] vaut", c[i])

6.1.3. Typage

Dans les exemples précédents on a vu que [0, 2, 4, 8, 13] et ["Un", "problème", "sans", "solution", "est", "un", "problème", "mal", "posé"] sont des séquences.

Peut on mélanger les chaînes de caractères et les nombres dans une séquence ?

c = [1, "problème", "sans", "solution", "est", 1, "problème", "mal", "posé"]

Oui. Cette déclaration est tout à fait valide.

Il est même possible de déterminer le type de chaque élément :

c = [1, "problème", "sans", "solution", "est", 1, "problème", "mal", "posé"]
print(c)
for i in range(len(c)):
    print(type(c[i]))

Ce qui produit le résultat suivant :

[1, 'problème', 'sans', 'solution', 'est', 1, 'problème', 'mal', 'posé']
<class 'int'>
<class 'str'>
<class 'str'>
<class 'str'>
<class 'str'>
<class 'int'>
<class 'str'>
<class 'str'>
<class 'str'>

6.1.4. Slicing

La simplicité est encore plus évidente quand on aborde le slicing (découpage) :

alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
print(alpha[4:16])

Ce qui produit le résultat suivant :

EFGHIJKLMNOP

En C on utiliserait une fonction comme strncpy() ou memcpy(), plus complexes et dangereuses pour les débutants, et qui nécessitent une allocation mémoire.

En Python, les lignes suivantes :

alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
print(alpha[20:100])

Produisent le résultat suivant :

UVWXYZ

En clair, on a demandé une tranche de la chaîne s, située entre le 20ème caractère et le 100ème, alors que la chaîne a une longueur de seulement 26 caractères. On l'aura compris, Python est un langage qui apporte de la sécurité.

Comment récupérer le dernier élément d'une séquence ? comme ceci :

a = "abcdef"
print(a[-1])

6.1.5. Introspection

On appelle introspection la capacité à examiner les classes, fonctions et mots-clés pour savoir ce qu'ils sont, ce qu'ils font et ce qu'ils connaissent.

Écrivons une classe et un peu de code :

class student:
    name = ""
    present = False
    # constructeur
    def __init__(self, name):
        self.name = name
    # methods
    def enter(self):
        ''' the student enters the school '''
        self.present = True
    def exit(self):
        ''' the student leaves school '''

        self.present = False

john = student("john")
print("john:")
print(john)
print("id(john):")
print(id(john))
print('dir("john"):')
print(dir(john))
print('john.__class__:')
print(john.__class__)
print('dir(john.present):')
print(dir(john.present))
print('dir(john.enter):')
print(dir(john.enter))
print('callable(getattr(john, "enter")):')
print(callable(getattr(john, "enter")))

Ce qui produit le résultat suivant :

john:
<__main__.student object at 0x7024a0d6ef00>
id(john):
126360425639824
dir("john"):
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'enter', 'exit', 'name', 'present']
john.__class__:
<class '__main__.student'>
dir(john.present):
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'is_integer', 'numerator', 'real', 'to_bytes']
dir(john.enter):
['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__func__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
callable(getattr(john, "enter")):
True

Examinons de plus près :

print("john:")
print(john)
john:
<__main__.student object at 0x7024a0d6ef00>

print(john) dit que john est un objet de la classe student situé dans le module __main__, et que son identifiant est 0x7024a0d6ef00

print("id(john):")
print(id(john))
id(john):
126360425639824

id(john) nous donne l'identifiant de l'objet john : 126360425639824 (0x7024a0d6ef00 en décimal)

print('dir("john"):')
print(dir(john))
dir("john"):
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'enter', 'exit', 'name', 'present']

dir(john) retourne les membres de l'objet john. Les méthodes que nous avons écrites sont présentes, parmi d'autres membres.

print('john.__class__:')
print(john.__class__)
john.__class__:
<class '__main__.student'>

john.__class__ contient le nom de la classe de l'objet john : __main__.student

print('dir(john.present):')
print(dir(john.present))
dir(john.present):
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'is_integer', 'numerator', 'real', 'to_bytes']

dir(john.present) retourne les attributs de l'attribut present de l'objet john.

print('dir(john.enter):')
print(dir(john.enter))
dir(john.enter):
['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__func__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

dir(john.enter) retourne les attributs de la méthode enter() de l'objet john.

print('callable(getattr(john, "enter")):')
print(callable(getattr(john, "enter")))
callable(getattr(john, "enter")):
True

callable(getattr(john, "enter")) permet de savoir si le membre enter de l'objet john est appelable.

On peut remarquer sur cette ligne l'utilisation de simples et doubles quotes imbriquées :

print('callable(getattr(john, "enter")):')

Ce qui permet de ne pas fermer la chaîne sur la première double quote de "enter".

On aurait pu écrire aussi :

print("callable(getattr(john, 'enter')):")

Ou, comme en C :

print("callable(getattr(john, \"enter\")):")

En Python on peut indifféremment utiliser la simple ou la double quote pour encadrer une chaîne, comme en JavaScript.

On constate qu'en Python tout est orienté objet, qu'il s'agisse de classe, d'instances, d'attributs et méthodes. Je vous laisse imaginer les possibilités offertes par toutes ces méthodes d'introspection.

6.1.6. Et les pointeurs ?

En Python, la notion de pointeur n'existe tout simplement pas. C'est certainement une bonne nouvelle pour les débutants.

6.2. La ligne série

Quand on travaille avec un ARDUINO ou un ESP, il est assez courant d'avoir recours au moniteur série pour afficher des information sur l'écran du PC.

Mais c'est assez limité. Que faire si l'on a besoin d’interagir avec le logiciel embarqué ? La ligne série, accessible via le connecteur USB de la carte, semble le moyen le plus simple.

J'ai déjà traité ce sujet ici : Commander un ARDUINO par la ligne série ou BLUETOOTH

Pour communiquer en série à partir de Python, il vous faudra installer le package PySerial.

Certains d'entre vous ont peut être déjà développé des logiciel de communication série en C à l'aide de Win32 sous Windows, ou termios sous Linux. Sous Windows, on peut très vite constater que cela peut très vite provoquer des problèmes en fonction de la cible. On logiciel peut très bien fonctionner sur certains PC et mal fonctionner sur d'autres (c'est du vécu). Il faut vraiment être un spécialiste de Win32 pour s'en sortir. Sous Linux c'est beaucoup moins le cas.

Mais dans tous les cas, avec PySerial votre code fonctionnera aussi bien sur Windows que MacOs ou Linux. Seule l'ouverture de la ligne série diffère :

Sous Linux :

ser = serial.Serial('/dev/ttyUSB0')

Sous Windows :

ser = serial.Serial('COM1')

6.3. L'interface graphique

En matière d'interface graphique, on peut en Python se contenter de TkInter, qui produit des résultats honorables mais assez peu esthétiques :

Interface graphique Tkinter python

On peut également utiliser WxPython, PyGTK, ou PyQT, qui sont des interfaces Python à WxWindows, GTK et QT.

On peut citer également PyGame, une librairie de dessin toute simple, sans widgets.

Mais à l'heure actuelle ces librairies sont de moins en moins utilisées, la création d'interfaces graphiques se faisant principalement en HTML, JavaScript et CSS.

6.4. Le serveur WEB

Il existe une grande variété de serveurs WEB écrits en PYTHON :

  • http.server
  • CherryPy
  • Django
  • Flask
  • etc

6.4.1. http.server

http.server fait partie de la librairie standard Python. Examinons cet exemple simple :

from http.server import BaseHTTPRequestHandler, HTTPServer
import time
 
hostName = "127.0.0.1"
serverPort = 8080
 
class MyServer(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.end_headers()
        self.wfile.write(bytes("<html><head><title>My Web Server</title></head>", "utf-8"))
        self.wfile.write(bytes("<body>", "utf-8"))
        self.wfile.write(bytes("<p>This is my web server</p>", "utf-8"))
        self.wfile.write(bytes("</body></html>", "utf-8"))
 
if __name__ == "__main__":        
    webServer = HTTPServer((hostName, serverPort), MyServer)
    print("Server started at http://%s:%s" % (hostName, serverPort))
 
    try:
        webServer.serve_forever()
    except KeyboardInterrupt:
        pass
 
    webServer.server_close()
    print("Server stopped.")

Si l'on lance ce serveur en local il affichera sur la console :

Server started at http://127.0.0.1:8080

Dans le navigateur on entrera :

http://localhost:8080/index

ou :

http://localhost:8080/

Le navigateur affichera simplement : This is my web server

On peut ajouter une URL supplémentaire nommée alternative :

from http.server import BaseHTTPRequestHandler, HTTPServer
import time

hostName = "127.0.0.1"
serverPort = 8080

class MyServer(BaseHTTPRequestHandler):
    def do_GET(self):
        if(self.path == '/' or self.path == '/index'):
                self.send_response(200)
                self.send_header("Content-type", "text/html")
                self.end_headers()
                self.wfile.write(bytes("<html><head><title>My Web Server</title></head>", "utf-8"))
                self.wfile.write(bytes("<body>", "utf-8"))
                self.wfile.write(bytes("<p>This is my web server</p>", "utf-8"))
                self.wfile.write(bytes("</body></html>", "utf-8"))
        elif(self.path == '/alternative'):
                self.send_response(200)
                self.send_header("Content-type", "text/html")
                self.end_headers()
                self.wfile.write(bytes("<html><head><title>My Web Server</title></head>", "utf-8"))
                self.wfile.write(bytes("<body>", "utf-8"))
                self.wfile.write(bytes("<p>This is another URL</p>", "utf-8"))
                self.wfile.write(bytes("</body></html>", "utf-8"))

if __name__ == "__main__":
    webServer = HTTPServer((hostName, serverPort), MyServer)
    print("Server started at http://%s:%s" % (hostName, serverPort))

    try:
        webServer.serve_forever()
    except KeyboardInterrupt:
        pass

    webServer.server_close()
    print("Server stopped.")

Dans le navigateur on entrera :

http://localhost:8080/alternative

Le navigateur affichera alors : This is another URL

Comme on le voit, la librairie est assez spartiate. On a franchement l'impression de travailler à très bas niveau. 

6.4.2. CherryPy

Cherrypy propose une approche intéressante :

import cherrypy
 
class HelloWorld(object):
    @cherrypy.expose
    def index(self):
        return "Hello from CherryPy!"
 
if __name__ == '__main__':
    cherrypy.quickstart(HelloWorld())

Chaque méthode de la classe HelloWorld est exposée à l'aide du mot clé (appelé décorateur) @cherrypy.expose. Elle sera donc appelée lorsque l'URL nommée index sera entrée dans la barre d'adresse du navigateur, mais également l'URL nommée /.

Si l'on lance ce serveur en local il affichera sur la console :

[06/Dec/2025:15:54:57] ENGINE Listening for SIGTERM.
[06/Dec/2025:15:54:57] ENGINE Listening for SIGHUP.
[06/Dec/2025:15:54:57] ENGINE Listening for SIGUSR1.
[06/Dec/2025:15:54:57] ENGINE Bus STARTING
CherryPy Checker:
The Application mounted at '' has an empty config.
[06/Dec/2025:15:54:57] ENGINE Started monitor thread 'Autoreloader'.
[06/Dec/2025:15:54:57] ENGINE Serving on http://127.0.0.1:8080
[06/Dec/2025:15:54:57] ENGINE Bus STARTED

A noter que le port par défaut du serveur est le 8080.

Dans le navigateur on entrera :

http://localhost:8080/index

ou :

http://localhost:8080/

Le navigateur affichera simplement : Hello from CherryPy!

On peut ajouter une URL supplémentaire nommée alternative :

import cherrypy
 
class HelloWorld(object):
    @cherrypy.expose
    def index(self):
        return "Hello from CherryPy!"
 
    @cherrypy.expose
    def alternative(self):
        return "Another Hello from CherryPy!"

if __name__ == '__main__':
    cherrypy.quickstart(HelloWorld())

Dans le navigateur on entrera :

http://localhost:8080/alternative

Le navigateur affichera alors : Another Hello from CherryPy!

Simplissime ! Au final, pour ceux qui connaissent, comparer http.server à CherryPy revient à comparer les classes WebServer et AsyncWebServer quand on travaille sur ESP32. Quand on a goûté à CherryPy ou AsyncWebServer, il est difficile de revenir à http.server ou WebServer. On s'habitue vite au luxe.

Comme vous le savez peut être, AsyncWebServer est capable de gérer une page HTML en utilisant une template (un modèle). En Python la gestion des templates peut être confiée à Jinja2.

7. Générer un exécutable sous Windows

Quand on travaille avec Python, il est assez facile de distribuer ses logiciels à des utilisateurs Linux. En effet, Python est très souvent pré-installé sur la majeure partie des distributions.

Sous Windows, ce n'est pratiquement jamais le cas. Mais il existe un moyen : PyInstaller permet de générer un exécutable, un .exe !

Oui, vous avez bien lu. Vous pourrez ainsi distribuer votre logiciel de manière extrêmement simple, et de plus sans divulguer vos fichiers sources. La suite ici.

8. Conclusion

D'après de nombreux spécialistes Python est un langage de haut niveau, simple et facile à apprendre. De plus, de nombreuses librairies sont disponibles, ce qui simplifie encore plus le développement d'applications.

Malgré cela il a un inconvénient majeur : du fait que c'est un langage interprété, certaines erreurs de syntaxe sont détectées lors de l'exécution. Cela veut dire clairement que la phase de tests est un élément crucial dans le processus de développement. Mais cela ne veut pas dire que cela le rend moins fiable pour autant. En matière de développement, la phase de tests est en général aussi coûteuse que la phase de codage, quel que soit le langage. Il ne viendrait pas à l'idée d'un développeur professionnel de négliger ce fait.

Tout langage nécessite un apprentissage, qui requiert en général une semaine de labeur. Pour accéder à Python il n'en sera pas autrement, mais l'investissement sera très vite rentabilisé, bien au delà de ce que peut offrir C ou C++.


Cordialement

Henri

mercredi 29 mai 2024

ESP32 : Comparons


ESP32 : Comparons


La famille de µcontrôleurs ESP32 s'est étoffée depuis quelques années :

  • ESP32
  • ESP32 S2
  • ESP32 C3
  • ESP32 S3

Voici deux pages résumant les caractéristiques des différents modèles :

Chip Series Comparison

Comparison table for ESP8266/ESP32/ESP32-S2/ESP32-S3/ESP32-C3/ESP32-C6

Les cartes basées sur l'ESP32 sont extrêmement nombreuses. On ne va pas les citer toutes ici. Etant assez fan des cartes WEMOS D1 mini et LOLIN D32, je me suis naturellement tourné vers les cartes de ce constructeur.

1. ESP32

L'excellente FIREBEETLE reste un modèle à privilégier si l'on désire une carte pour un projet fonctionnant sur batterie :

Son régulateur RT9080 lui assure une consommation en veille de 10µA.

La WEMOS LOLIN32 est une carte déjà ancienne, disposant également d'un connecteur batterie :


Sa consommation en veille est de 1.7mA.

2. ESP32 S2

Historiquement, l'ESP32 S2 est le premier modèle sorti. Il apporte une nouveauté : il intègre nativement un contrôleur USB, ce qui permet de se passer de convertisseur USB / série. Le C3 et le S3 sont équipés d'USB de la même façon.

WEMOS LOLIN S2 mini

Ces cartes ont un format particulièrement réduit, par contre la double rangée de broches empêche de les implanter sur une breadboard.

Attention : il semblerait que l'ESP32 S2 ne possède pas de BLUETOOTH BLE, ni de DAC.

3. ESP32 C3

WEMOS LOLIN C3 mini

Cette carte, par rapport à la précédente, n'intègre plus une antenne imprimée, mais une antenne CMS (le petit composant rouge). Certaines critiques laissent à penser que la portée de ces antennes serait inférieure.

A noter la présence d'une LED adressable WS2812B.

4. ESP32 S3

WEMOS LOLIN S3 mini

L'antenne est imprimée, comme sur le S2. Ici, aussi une LED adressable est présente.

Pour ceux qui préfèrent les cartes un peu mieux équipées, un modèle PRO existe :

WEMOS LOLIN S3 pro

Elle possède entre autres un connecteur batterie, et bien entendu un chargeur.

5. Préparatifs

5.1. IDE ARDUINO

Pour que le core ESP32 apporte le support des µcontrôleurs C3, S2 et S3, C3, il faut installer une version récente :

Installing the ESP32 Board in Arduino IDE (Windows, Mac OS X, Linux)

Si une version ancienne est installée, il suffit de la mettre à jour à l'aide du gestionnaire de cartes : 

Menu Outils / Type de carte / Gestionnaire de cartes. Filtrez votre recherche en tapant ESP32.

5.2. LED adressable

Le pilotage de la LED adressable nécessite l'installation de la librairie Adafruit NeoPixel, que l'on peut trouver dans le gestionnaire de bibliothèques :

Menu Outils / Gestionnaire de bibliothèques. Filtrez votre recherche en tapant neopixel.

5.3. Serial monitor

Pour que les sorties que l'on imprime à l'aide de Serial soient visibles dans le terminal, il faut sélectionner :

Menu Outils / USB CDC On Boot : enabled

6. Test

J'ai réalisé quelques tests avec une carte WEMOS LOLIN S3 mini. 

6.1. LED

Testons tout d'abord la LED embarquée :

#include <Adafruit_NeoPixel.h>

int neoPixelPin = 47;

Adafruit_NeoPixel strip = Adafruit_NeoPixel(1, neoPixelPin, NEO_GRB + NEO_KHZ800);

void setup() {
  Serial.begin(115200);
  strip.begin();  // initialize the strip
  strip.show();   // make sure it is visible
  strip.clear();  // Initialize all pixels to 'off'
  Serial.println("GO");
}

void loop() {
  strip.setBrightness(100);
  strip.setPixelColor(0, 0, 255, 0);
  strip.show();
  delay(1000);
  strip.setPixelColor(0, 255, 0, 0);
  strip.show();
  delay(1000);
  strip.setPixelColor(0, 0, 0, 255);
  strip.show();
  delay(1000);
}

La LED doit s'allumer alternativement en rouge, vert, bleu.

6.2. WebServer

L'exemple HelloServer de la librairie ESP32 permet d'afficher "hello from esp32!" sur un navigateur.

Dans cet exemple, bien entendu, il faut veiller à remplacer les identifiants par les vôtres :

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

L'exemple fonctionne sans problème. Le moniteur série affiche : 

Connected to XXXXXXXXXX
IP address: 192.168.1.20
MDNS responder started
HTTP server started

Entrer l'adresse indiquée dans la barre d'adresse de votre navigateur. Celui-ci affiche :

hello from esp32!

Bien entendu l'exemple est simpliste, mais je n'ai aucune raison de douter qu'un logiciel plus complexe pose des problèmes.

7. Conclusion

Ce premier contact avec cette carte WEMOS LOLIN S3 mini est plutôt positif.

J'ai l'impression que la connexion au WIFI est plus rapide qu'avec les autres cartes que j'ai testé. Cela reste à confirmer.

Pour ma part le principal intérêt de ces cartes mini est leur compacité. Elle ont la même taille qu'une WEMOS ESP8266 D1 mini, pour un nombre de GPIOs bien supérieur (sauf pour la C3).

Dans un prochain article je testerai l'aptitude de cette carte à servir de base à des projets basse consommation. 

Je doute que je puisse parvenir à une consommation ultra-basse, équivalente à celle d'une FIREBEETLE. Rien que la présence de la LED adressable ne me dit rien qui vaille, car ce genre de LED consomme, même si l'on ne s'en sert pas. Mais rien n'empêche de la supprimer.


Cordialement

Henri


samedi 14 octobre 2023

ESP32 : servir une image


ESP32 : servir une image

 

Lorsque l'on développe un WebServer sur ESP32, on utilise différentes librairies : 

  • WifiServer
  • WebServer
  • EspAsyncWebServer
  • etc.

Je ne vais pas rappeler les multiples avantages d'EspAsyncWebServer, mais il peut entre autres :

  • servir des fichiers HTML, JavaScript, images, etc. à partir du système de fichiers en FLASH
  • gérer des templates à la manière de Cheetah sur un serveur PC

Le cas de WifiServer est à part. Son utilisation se rapproche de celle de la librairie Arduino Ethernet, ce qui veut dire que l'effort de développement est très important. Il n'est même pas capable de récupérer les arguments au sein d'une URL de manière simple. Je n'en parlerai donc pas.

Voyons le cas d'une image.

1. Avec EspAsyncWebServer

EspAsyncWebServer n'est pas une librairie présente dans le package ESP32 standard. Il faut l'installer : 

  • en la téléchargeant depuis https://github.com/me-no-dev/ESPAsyncWebServer
  • à l'aide du gestionnaire de bibliothèques de l'IDE ARDUINO

Ce petit exemple renvoie une image au client :

#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <SPIFFS.h>

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

AsyncWebServer server(80);

void handleNotFound(AsyncWebServerRequest * request) {
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += request->url();
  message += "\nMethod: ";
  message += request->methodToString();
  message += "\nArguments: ";
  message += request->args();
  message += "\n";
  for (uint8_t i = 0; i < request->args(); i++) {
    message += " " + request->argName(i) + ": " + request->arg(i) + "\n";
  }
  request->send(404, "text/plain", message);
}

void setup(void) {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  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());

  if (!SPIFFS.begin(true)) {
    Serial.println("An Error has occurred while mounting SPIFFS");
    return;
  }
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send(200, "text/plain", "hello from esp32!");
  });
  server.on("/arduino.jpg", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send(SPIFFS, "/arduino.jpg", "image/jpg");
  });

  server.onNotFound(handleNotFound);

  server.begin();
  Serial.println("HTTP server started");
}

void loop(void) {
}

Ce code renvoie une image nommée arduino.jpg située à la racine du système de fichiers SPIFFS. Deux lignes suffisent (en gras).

2. Avec WebServer

WebServer est la librairie par défaut présente dans le package ESP32 standard.

Elle nécessite un effort de programmation un peu plus conséquent : 

    #include <WiFi.h>

    #include <WebServer.h>
    #include <FS.h>
    #include <SPIFFS.h>

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

    WebServer server(80);

    void handleRoot()
    {
      server.send(200, "text/plain", "hello from esp32!");
    }

    void handleImage()
    {
      File file = SPIFFS.open("/arduino.jpg");
      if (!file) {
        Serial.println("Failed to open file for reading");
        return;
      }
      if (server.streamFile(file, "image/jpg") != file.size()) {
        Serial.println("Sent less data than expected!");
      }
      file.close();
    }

    void handleNotFound() {
      String message = "File Not Found\n\n";
      message += "URI: ";
      message += server.uri();
      message += "\nMethod: ";
      message += (server.method() == HTTP_GET) ? "GET" : "POST";
      message += "\nArguments: ";
      message += server.args();
      message += "\n";
      for (uint8_t i = 0; i < server.args(); i++) {
        message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
      }
      server.send(404, "text/plain", message);
    }

    void setup(void) {
      Serial.begin(115200);
      WiFi.mode(WIFI_STA);
      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());

      if(!SPIFFS.begin(true)){
        Serial.println("An Error has occurred while mounting SPIFFS");
        return;
      }
      server.on("/", handleRoot);
      server.on("/arduino.jpg", handleImage);

      server.onNotFound(handleNotFound);

      server.begin();
      Serial.println("HTTP server started");
    }

    void loop(void) {
      server.handleClient();
    }

    Le code utilisé pour la gestion de l'image est également en gras. Il est un peu plus complexe. Il faut bien connaître la librairie WebServer pour s'en sortir, en particulier l'existence de la méthode streamFile().

    3. Les points communs

    Dans les deux cas, le fichier arduino.jpg doit être chargé dans le système de fichier SPIFFS. Ce fichier doit être créé dans un sous-répertoire data du projet : 

    projet / projet.ino

    ........../ data / arduino.jpg

    Le fichier doit être chargé dans la mémoire FLASH de l'ESP32 à l'aide du menu "Outils/ESP32 Sketch Data Upload".

    Avant cela, si ce n'est pas déjà fait, il faut installer le plugin :

    https://github.com/me-no-dev/arduino-esp32fs-plugin

    Pour essayer ces deux exemples, il suffit de les charger dans un ESP32, après avoir modifié ces deux lignes :

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

    Il suffit de remplacer xxxxx par vos identifiants WIFI.

    Ensuite entrez dans votre navigateur l'adresse IP affichée dans le serial monitor. Il affichera ceci :

    hello from esp32!

    Si vous ajoutez /arduino.jpg à l'URL (exemple : http://192.168.1.27/arduino.jpg) Il affichera l'image que vous avez choisi.

    On peut bien évidemment intégrer l'image dans une page HTML de son choix à l'aide de la balise adéquate : 

    <img src="arduino.jpg">

    4. Conclusion

    Bien que la version WebServer ne soit pas d'une complexité insurmontable, la version EspAsyncWebServer est nettement préférable, surtout si l'on considère que l'on pourra stocker dans SPIFFS non seulement des images, mais aussi le code HTML, et éventuellement JavaScript.

    Servir un fichier HTML ou JavaScript sera extrêmement simple : 

      server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
        request->send(SPIFFS, "/index.html", "text/html");
      });
      server.on("/example.js", HTTP_GET, [](AsyncWebServerRequest * request) {
        request->send(SPIFFS, "/example.js", "text/javascript");
      });

    Ensuite il reste à explorer les templates, qui prendront en charge les données variables des pages HTML.

    https://techtutorialsx.com/2018/07/22/esp32-arduino-http-server-template-processing/

    Les templates sont le point fort d'EspAsyncWebServer, et permettent de développer très simplement des serveurs assez conséquents.


    Cordialement

    Henri


    mardi 15 août 2023

    ESP32 : Micro-irrigation Connectée (3ème partie)

     


    ESP32 : Micro-irrigation Connectée (3ème partie)


    Cet article fait suite à ceux-ci :

    ESP32 : Micro-irrigation Connectée (1ère partie)

    ESP32 : Micro-irrigation Connectée (2ème partie)

    Pour rappel, le projet se trouve ici :

    https://bitbucket.org/henri_bachetti/esp32-sprinkle-timer

    1. Le hardware

    Du côté matériel, le capteur d'humidité et le capteur de débit sont présents : 

    • capteur d'humidité : GPIO36 (broche SVP)
    • capteur de débit : GPIO39 (broche SVN)

    Contrairement à ce que je pensais, l'ESP32 est tout à fait capable de mesurer une tension analogique de 3.3V, ou plutôt la librairie ARDUINO fait ce qu'il faut pour que cela soit possible. Le pont diviseur R3 R5 disparaît du schéma (paragraphe 5).

    En ce qui concerne le capteur de débit, la mesure renvoie des valeurs non nulles pour un débit de zéro. Il faut que je fasse des essais afin de voir s'il ne s'agirait pas d'un problème d'alimentation. Il est alimenté par la broche 5V de l'ESP32 et celui-ci est alimenté par l'USB. Comme il ne reste que 4.7V, ce n'est peut-être pas suffisant.

    L'écran OLED est connecté, ainsi que le bouton fonction et arrosage manuel: 

    • SDA : GPIO21
    • SCL : GPIO22
    • bouton fonction : GPIO35
    • bouton arrosage manuel : GPIO34

    Comme il n'y a pas de résistance de PULLUP interne sur les GPIOS 35 et 34, il faut en ajouter une sur le schéma entre GPIO35, GPIO34 et 3.3V : 10KΩ.

    2. La configuration

    2.1. config.ini

    Le paramétrage des capteurs a été ajouté :

    [moisture]
    sensor=36
    max=50

    [flow]
    sensor=39
    max=10

    2.2 Schedule.ini

    Ce fichier reste inchangé, mis à part que deux périodes d'arrosage ont été ajoutées :

    [courges.voie1]
    schedule1=08:00,15

    [massif.voie1]
    schedule1=08:30,15

    3. L'interface HTML

    La page d'accueil comporte maintenant des boutons permettant de modifier les périodes d'arrosage et même d'en ajouter : 

    Cliquer sur l'un des boutons CONFIGURE permet d'accéder à un formulaire :

    Il suffit de modifier les valeurs et de cliquer sur OK pour enregistrer les changements. Les changements sont stockés dans le fichier schedule.ini.

    En cliquant sur le bouton AJOUTER on accède à un formulaire à peine différent :

    Le formulaire propose une liste des zones existantes.

    Attention il n'y a pas pour l'instant de contrôle de validité sur ces deux formulaires. Il faut bien respecter le format de saisie HH:MM.

    Les durées sont exprimées en minutes.

    Le bouton maintenance permet d'accéder au menu suivant :

    Les actions suivantes sont possibles :

    • tester les relais
    • afficher le fichier config.ini
    • afficher le fichier schedule.ini

    Il est donc possible de sauvegarder ces deux fichiers avant une mise à jour des fichiers HTML. Les fichiers HTML et les fichiers de configuration (.ini) sont situés dans le même répertoire data du projet. Les fichiers de configuration seraient donc écrasés à la prochaine mise à jour si l'on ne prend pas de précautions.

    4. L'écran OLED

    L'afficheur montre la date et l'heure, ainsi que l'humidité et le débit :

    Un appui sur le bouton fonction permet l'affichage de l'adresse IP de l'ESP32 pendant 4 secondes, puis de l'heure du prochain arrosage pendant 4 secondes, puis l'affichage revient à la normale.

    Un appui sur le bouton d'arrosage manuel provoque l'affichage du premier arrosage manuel possible et de sa durée :

    En appuyant plusieurs fois sur ce bouton on peut faire défiler les noms des voies. Un appui sur le bouton fonction déclenche l'arrosage manuel sur la voie choisie.

    5. Rappels

    Ce logiciel a besoin d'être compilé en utilisant les librairies suivantes :

    ESPAsyncWebServer : https://github.com/me-no-dev/ESPAsyncWebServer

    SPIFFSIniFile : https://github.com/yurilopes/SPIFFSIniFile

    SSD1306 : https://github.com/adafruit/Adafruit_SSD1306

    GFX : https://github.com/adafruit/Adafruit-GFX-Library

    ezButton : https://github.com/ArduinoGetStarted/button

    Quelques constantes peuvent être modifiées dans le code :

    // max line length in config file
    #define MAX_LINE 250
    // max definition length for object names (relays, ways, zones, etc.)
    #define MAX_DEF 50
    #define MAX_BUF 30

    // max ways and relays number
    #define MAX_WAY 128
    // max zones number
    #define MAX_ZONE 12
    // max waterings number in a day for each way
    #define MAX_SCHEDULE 4
    // max waterings number in a day for all ways
    #define MAX_WATERING 256

    On voit par exemple que le nombre d'arrosages par voie est limité à 4 par jour, ce qui me semble amplement suffisant. On peut bien entendu ajuster certaines valeurs, mais attention, celles-ci ne sont pas modifiables ultérieurement par l'interface HTML.

    Pour le reste, relire l'article précédent : 5. Essayer

    6. Le code

    Le code, comme on peut s'en douter, n'est pas constitué d'un seul sketch, il serait énorme et illisible.

    Voici une liste des fichiers :

    esp32-sprinkle-timer.ino : le sketch principal
    config.cpp, config.h : lecture du fichier de configuration config.ini
    constants.h : constantes
    flow.cpp, flow.h : capteur de débit
    hmi.cpp, hmi.h : interface homme/machine sur l'écran OLED
    html.cpp, html.h : interface homme/machine HTML
    humidity.cpp, humidity.h : capteur d'humidité
    module.cpp, module.h : gestion des modules (GPIOs, MCP23017, etc.)
    oled.cpp, oled.h : écran OLED
    relay.cpp, relay.h : gestion des relais
    schedule.cpp, schedule.h : lecture du fichier de configuration schedule.ini
    valve.cpp, valve.h : gestion des vannes
    watering.cpp, watering.h : gestion des arrosages
    way.cpp, way.h : gestion des voies
    zone.cpp, zone.h : gestion des zones

    Je veux bien admettre que ce code commence à devenir très conséquent, mais la complexité ne se gère pas avec simplicité. En opérant un découpage en modules simples, on peut toutefois réduire cette complexité et améliorer la lisibilité.

    7. Conclusion

    Les difficultés sont immenses car ce logiciel est hyper-paramétrable, et le temps manque. En effet, on peut difficilement s'occuper d'un jardin de 45m², récolter, cuisiner, faire des conserves, gérer un poulailler, aménager une nouvelle habitation, modifier son circuit électrique, et faire du développement logiciel en parallèle.

    La période hivernale sera certainement plus productive en lignes de code. Patience ...


    Cordialement

    Henri

    8. Mises à jour

    18/08/2023 : ajout de l'écran OLED et du bouton fonction
    22/08/2023 : ajout du bouton d'arrosage manuel