Modification du contenu d'une page Web en Python

Dans l'article précédent, un serveur Web avait été créé pour exposer des pages Web sur un réseau domestique. Pages Web que l'on peut consulter sur un autre ordinateur connecté au même réseau avec un simple navigateur en tapant simplement l'adresse IP de la brique comme url.
Afficher des pages Web exposées par une brique Lego EV3 ne présente aucun intérêt car les pages Web étant statiques, leur contenu doit être déterminé a priori. Cependant, le langage HTML et les technologies associées (CSS, JavaScript), utilisés pour la construction de celles-ci, permettent de créer des IHM (Interface Homme-Machine) extrêmement conviviales, dépassant de très loin ce que permet l'écran LCD minuscule noir & blanc dont est dotée la brique. De plus, ces données sont consultables à distance sans avoir à courir après le robot, quand celui-ci est mobile, pour tenter d'apercevoir les données collectées par les capteurs utilisés. Encore faut-il pouvoir formaliser ces données dans une page Web par programmation.
L'objet de cet article est de montrer comment modifier le contenu du page dans un script Python. Pour ne pas compliquer les choses, il ne sera pas fait ici appels aux capteurs Lego. La donnée modifiée sera affichée dans une zone <div></div> HTML dont l'identifiant permet de la repérer.
Cet article, et les articles traitant de l'utilisation de serveur Web sur la brique EV3, sera développé dans le Workspace VSCode TestWeb.

Pré-requis

La page Web à afficher

La page web à affichée est créée dans le fichier test.html.  Celui-ci peut être crée avec VSCode dans le répertoire du projet TestWeb. De cette façon, celle-ci sera téléchargée sur la brique en même temps que le script Python au moment du test du programme.
Voici le contenu du fichier test.html :
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Page Test</title>
<style>
#zone {
width:400px;
margin:auto;
padding:20px;
font-family: 'Courier New', Courier, monospace;
font-size: xx-large;
text-align: right;
border: 2px solid red;
}
</style>
</head>
<body>
<h1>Test d'une zone modifiable</h1>
<p>Cette page permet de tester la modification dynamique d'une zone.</p>
<div id="zone">000000</div>
<p>La zone ci-dessus est modifiée toutes les secondes.
        Pour afficher le nouveau contenu, rafraîchir la page
        en tapant F5 dans le navigateur.
    </p>
</body>
</html>
Quelques commentaires sont nécessaire pour expliquer le contenu de cette page Web :
  • Une page Web est constituée d'un élément racine unique <html>.
  • Celui-ci contient deux éléments :
    • <head> contient des métadonnées concernant la page.Son contenu n'est pas affiché par le navigateur.
    • <body> constitue la page Web proprement dite. Seul son contenu est affiché par le navigateur.

Explications du contenu de l'élément <head>

  • L'élément <meta>, tel qu'il est exprimé permet de définir le type d'encodage utilisé. L'encodage UTF8 permet de ne pas avoir recours à un codage compliqué des caractères accentués en HTML. 
  • L'élément <title> définit le titre de la page. Il ne faut pas le confondre avec l'élément <h1> contenu dans l'élément <body>. Le titre, ici, ne fait pas partie du contenu affichable de la page. Cependant, certains navigateur l'affichent dans la barre de leur fenêtre.
  • Le langage HTML est très pauvre en capacités de présentation. Ceci est compensé par la technologie CSS dont les fonctionnalités permettent d'être extrêmement précis pour la présentation d'une page Web. De plus, il est d'une bonne pratique de séparer la partie présentation de la partie données. L'élément <style> permet d'embarquer des instructions CSS dans une page Web.

Explications du contenu de l'élément <body>

  • L'élément <h1> permet d'afficher le titre de la page. Il ne doit pas être être confondu avec l'élément <title> contenu dans l'élément <head> dont l'usage est différent.
  • Le premier élément <p> contient un léger descriptif de la page. Il est affiché comme un paragraphe.  
  • L'élément <div> suivant est la zone qui sera modifiée par le script Python. Pour pouvoir l'identifier de façon univoque, l'attribut id permet de lui associer un identifiant zone. Son contenu, actuellement arbitraire, est fixé à 000000.
  • Le second élément <p> donne des explications sur ce que peut faire l'utilisateur dans cette page.

Explications sur le contenu CSS de l'élément <style>

  • Le contenu de l'élément <style> permet de définir la présentation de la page Web. Ici, seul la présentation de la zone est défini.
  • La syntaxe #zone { ... } définit la présentation pour un élément quelconque de la page dont l'identifiant défini par un attribut id a la valeur zone. Le caractère # permet de définir la valeur de l'identifiant de l'élément concerné. Tous les styles définis entre les accolades ne concernent que cet élément.  
    • La largeur de l'élément est fixée à 400 pixels.
    • Les marges sont définies auto. L'élément est donc centré horizontalement et verticalement entre les éléments qui le précède et qui le suive.
    • Une marges interne est fixée à 20 pixels. Ce qui permet que le contenu de la zone ne soit pas collé aux bordures.
    • Le contenu de la zone est affiché avec la police de caractère Courrier New. Cette police a la caractéristique d'afficher les caractères avec un espacement constant. Ce qui est particulièrement adapté à l'affichage de données numériques.
    • Les caractères de la zone sont affichés avec la taille la plus grande..
    • Le texte  justifié à droite puisque  que les données affichées dans la zone sont numériques.
    • Une bordure rouge de de 2 pixels entoure la zone. Ce qui permet d'attirer dessus l'attention de l'utilisateur.
Comme on le voit, Le HTML et le CSS permet de définir une IHM plus riche que ne le permet l'écran LCD de la brique EV3.

Modification de la zone dans un script Python

L'objet de ce script est de modifier le contenu de la zone définie par l'élément <div> dans la page Web. Pour ne pas compliquer le problème et le circonscrire uniquement à la problématique Web, il ne sera pas fait appel à l'un des capteurs Lego utilisables avec la brique EV3. Cela sera traité dans un futur article qui fera la synthèse de toutes ces technologies.
Ce script est constitué d'une boucle sans fin dont chaque itération incrémente le nombre qui est affiché dans la zone
Le problème de ce script peut être décomposé par les sujets suivant :
  • Comment lire un fichier HTML pour accéder à son contenu.
  • Comment repérer un élément dont on connait l'identifiant
  • Comment modifier le contenu de cet élément
  • Comment mettre à jour la page Web pour que celle-ci puisse être consultée dans un navigateur avec son contenu actualisé.
Pour résoudre ce problème, on va utiliser la bibliothèque xml.etree de python.Cette bibliothèque possède les outils nécessaires pour manipuler les objets d'un arbre d'éléments balisés comme l'est la structure d'une page HTML.

Script Python testhtml.py

Ce script est créé avec VSCode dans le répertoire TestWeb.
#!/usr/bin/env python3

#1#
import xml.etree.ElementTree as ET
import time

#2#
pageWeb = ET.parse("test.html")
#3#
zone = pageWeb.find(".//*[@id='zone']")
#4#
zone_content = 0
while True:
    #5#
    zone.text = str(zone_content)
    #6#
    pageWeb.write("result.html")
    #7#
    time.sleep(1)
    #4#
    zone_content += 1
  1. En python les bibliothèque d'objets utilisées doivent être importées :
    • L'objet ElementTree de la bibliothèque etree est utilisé pour manipuler les éléments contenus dans la page Web. Un alias ET est créé sur cet objet pour alléger la syntaxe.
    • L'objet time est utiliser pour mettre en attente la boucle pendant une seconde.
  2. La première chose à faire est d'invoquer la méthode parse() sur l'objet ET. En passant en paramètre "test.html", cela revient à ouvrir le fichier test.html dans le même répertoire que le script Python, à le lire complètement et à construire en mémoire un arbre d'objets, chaque objet correspondant à un élément HTML de la page.
  3. Ensuite, il faut repérer, parmi tous les éléments HTML contenu dans l'arbre, celui dont l'identifiant id est égal à "zone". Pour cela, la bibliothèque etree dispose d'une méthode find() que l'on peut invoquer sur n'importe que élément de la page Web. Ici, on l'invoque directement sur la page Web elle-même.
    Le résultat de la méthode find() est un aussi un objet ElementTree. C'est celui relatif à l'élément  <div id="zone">000000</div> de la page Web.
    Le paramètre de la méthode find() est une chaîne de caractères contenant une requête XPath. XPath est un langage de requête standard (comme l'est  l'instruction SQL SELECT pour les bases de données relationnelles) pour extraire des données dans une arbre XML.
    La syntaxe .//*[@id='zone'] est assez particulière. En voici la signification :
    • Le point signifie que la recherche doit être effectuée à partir de l'élément courant. Comme la méthode find() a été invoquée sur la page Web elle-même, la recherche est effectuée sur toute la page.
    • Le double slash indique que tous les éléments de la page doivent être considérés quelque soit leur niveau d'imbrication dans l'arbre HTML.
    • L'étoile indique que tous les éléments de la page doivent être considérés que soit le nom de leur balise. Pour la zone en question, la balise est <div>. On aurait pu utiliser la syntaxe .//div[@id='zone']. Cependant, comme en HTML, deux éléments ne peuvent pas avoir le même identifiant id, il n'y a pas d’ambiguïté possible.
    • l'expression entre crochet correspond à une restriction (comme la clause SQL WHERE pour les bases de données relationnelles).
    • @id='zone' est la clause de restriction. Cela veut dire que l'on ne considère que les éléments dont l'attribut id est égal à "zone". Celui-ci identifiant de façon univoque un élément HTML, le résultat de la méthode find() est forcément l'élément relatif à la zone recherchée. L'usage de quottes ou de guillemets est indifférent aussi bien en Python qu'en XPath. Ce qui facilite la syntaxe  en évitant l'utilisation de caractères d'échappement qu'exigent d'autres langages informatiques.  
  4. Une boucle sans fin va incrémenté à chaque itération une variable zone_content préalablement initialisée à 0. 
  5. A chaque itération, cette variable zone_content est affecté au contenu de l'objet zone après avoir été converti en chaîne de caractères. En effet, une page Web ne contient que du texte. Le contenu texte d'un objet ElementTree est obtenu pas sa propriété text. Pour en modifier le contenu, il suffit de lui affecter une nouvelle valeur.
  6. L'arbre HTML en mémoire doit être écrit dans un fichier pour pouvoir être consulté à distance avec un navigateur. Ici, le choix a été fait de l'enregistrer dans un fichier différent  result.tml pour conserver le fichier original test.html. Le fichier à consulter, dont le contenu va changer, est donc result.html.
  7. La boucle est mise en attente pendant une seconde.

Test du script Python

Le script Python présenté ci-dessus peut être démarré normalement comme tous les autres scripts.
Si, à part le clignotement de la brique, il semble ne rien se passer, c'est normal. En effet, le script n'effectue aucune opération sinon modifier toutes les secondes le contenu d'un fichier texte.
En revanche, le fichier texte étant une page Web structurée en HTML, celle-ci peut être consultée à distance avec un navigateur. Mais ceci est conditionné par ce qu'un serveur Web soit bien en cours d'exécution sur la brique.

Lancement d'un serveur Web

Le démarrage d'un serveur Web sur la brique est effectué dans une session SSH, comme cela a été expliqué dans l'article précédent intitulé « Créer un serveur Web statique en Python sur la brique EV3 ».
robot@ev3dev:~$ nohup python3 -m http.server &
[1] 1420
nohup: ignoring input and appending output to 'nohup.out'
robot@ev3dev:~$ 

Consultation de la page

Le serveur Web a été démarré dans le répertoire home de l'utilisateur robot. En revanche, le script Python est exécuté dans le répertoire TestWeb. L'url de consultation est donc 192.168.1.150:8000/TestWeb/result.html. Pour expliquer :
  • 192.168.1.150 est l'adresse IP de la brique EV3 telle qu'elle apparaît en haut du LCD.
  • 8000 est le port part défaut du serveur Web http.server créé en Python.
  • TestWeb est le répertoire dans lequel a été développé le projet et dans lequel se trouvent le script Python teshtml.py et la page Web test.html et également, pendant l'exécution du script, la page Web result.html dont le contenu est constamment modifié.
En tapant F5, tel que suggéré sur la page, on peut constater que le nombre affiché dans le cadre rouge s'incrémente.
A remarquer, que du fait que l'on écrit la page modifiée dans un fichier différent que celui qui est lu au démarrage du script, le nombre affiché repart à zéro. De fait, la page originale peut elle-même être consultée par l'url 192.168.1.150:8000/TestWeb/test.html.

Conclusion

La possibilité d'exposer dans un serveur Web des données calculées par script Python vient d'être démontrée. Il a été aussi démontré que le langage HTML rend possibles des IHM beaucoup plus riches que ne le permet le petit écran LCD de la brique EV3. De plus ces IMH sont consultables à distance sur un ordinateur, à la condition que celui-ci soit connecté au même réseau.
Cependant la solution présentée comporte encore beaucoup de limites :
  • Les données exposées font partie intégrante d'un fichier. Chaque modification de la donnée exposée déclenche l'écriture de ce fichier sur la carte microSD qui héberge le système. Ces cartes étant limitées en écriture, ne résisteront pas très longtemps à cet usage.
  • Le serveur Web doit être démarré à part dans une session SSD. Ce qui en soi n'est pas un problème, puisque cela assure un découplage complet entre le fonctionnement du script et le serveur Web qui peut être activé ou désactiver à distance lorsque l'on veut consulter la donnée. Malgré tout, il peut être intéressant que le serveur Web ne fonctionne qu'avec le script et que son activation et sa désactivation soit liée au fonctionnement du script.
  • Cette solution crée une concurrence d'accès de ressource. En effet, pendant que le script écrit le fichier de la page Web modifie, le serveur Web sera bloqué pour la consultation de ce fichier. Et inversement, le script Python sera lui aussi bloqué si une opération de lecture par le serveur Web a commencé. Ce qui peut déclencher des dead locks.  
Un article futur présentera une solution pour remédier à ces inconvénients :

Commentaires

Posts les plus consultés de ce blog

Connecter ev3dev2 à Internet en WiFi

Connecter Visual Studio Code à un robot MindStorm EV3 avec ev3dev-browser

Installer les modules EV3DEV2 sur Python