A) Schéma de la structure d’un serveur Arduino
B) Initialisation en continu de la page HTML avec des valeurs en provenance de capteurs :
C) Enchaînement de pages
D) Appel d’une page HTML en passant des paramètres
E) Affichage des images dans une page HTML
F) Protection d’un site par password
G) Récupération du statut des capteurs et affichage dans un canevas
H) Enregistrement d’un fichier des accès
Les informations ci-dessous sont en grande partie inspirées de l’excellent tutorial de Starting Electronics
A) Schéma de la structure d’un Serveur Arduino :
Les fichiers exemples sont situés ici Le programme « Squelette » qui va présenter au client une page HTML « mapage.htm » stockée sur la carte SD du Shield Arduino Ethernet, le serveur Arduino retournera si l’utilisateur clique sur un bouton l’état d’un switch placé sur la broche 3 du shield (cf. page sur les contacts ) :
//************************************************************
//*** Lecture des caracteres en provenance d un client web **
//************************************************************
#include <SPI.h>
#include <Ethernet.h>
#include <SD.h>
//--- Declarations ---
byte mac[] = { 0x00, 0xAD, 0xBE, 0xEF, 0xFE, 0x50 }; //-- Addresse MAC à adapter
IPAddress ip(192,168,0,50); //-- Adresse IP à adapter
EthernetServer serveurHTTP(80); //-- objet serveur utilisant le port 80 = port HTTP
String msg=""; //-- pour réception chaine requete
File webFile; //-- pour afficher la page HTML
//--- Setup ---
void setup() {
Serial.begin(9600);
//-- Initialisation de la carte SD
Serial.println("Initializing SD card...");
if (!SD.begin(4)) {
Serial.println("ERREUR - La carte SD est indisponible!");
return;}
Serial.println("Carte SD OK");
if (!SD.exists("mapage.htm")) {
Serial.println("ERREUR - Fichier mapage.html absent !");
return;}
Serial.println("Fichier mapage.html OK");
//---- initialise le serveur ----
Ethernet.begin(mac, ip);
delay(1000);
Serial.print("Shield Ethernet OK : L'adresse IP du shield Ethernet est : " );
Serial.println(Ethernet.localIP());
serveurHTTP.begin();
Serial.println("Serveur Ethernet OK : Ecoute sur port 80 (http)");
}
//--- Traitement ---
void loop(){
EthernetClient client = serveurHTTP.available(); //-- Objet Client Web
if (client) { //-- Si un navigateur interroge l'URL de la carte Arduino
Serial.println ("... Reception demande client...");
////////////////// Réception requete //////////////////////////
boolean currentLineIsBlank = true;
msg="";
//-- Chargement des caractères
while (client.connected()) {
if (client.available()) { //-- Si octets disponibles en lecture
char c = client.read();
msg+=c;
if (c == '\n' && currentLineIsBlank) { //--Dernière ligne du client reçue est vide
Serial.println ("Reception requete terminee");
//-- Analyse de la requete reçue
Serial.println("------------ Analyse ------------");
requeteOk(client);
if (msg.indexOf("ajax_switch") > -1) {
Serial.println ("Requete GET AJAX valide !");
//-- envoi de la réponse HTTP ---
envoiReponse(client);
Serial.println("La reponse Ajax envoyee au client distant...");}
else { //-- si pas la chaine souhaitée on envoie page entiere
Serial.println("Envoi mapage.html");
webFile = SD.open("mapage.htm");
if (webFile) {while(webFile.available()) {
client.write(webFile.read());}} //-- Envoi de la page web au client
webFile.close();}
//-- Affichage de la requete HTTP sur le port série
Serial.print(msg);
msg = "";
break;}
//-- Chaque ligne de texte reçue se termine par \r\n
if (c == '\n') { //-- Fin de ligne reçue
currentLineIsBlank = true;}
else if (c != '\r') { //-- Un caractère en provenance du client est reçu
currentLineIsBlank = false;}}}
//-- fermeture de la connexion avec le client après envoi réponse
delay(1);
client.stop();
Serial.println("------------ Fermeture de la connexion avec le client ------------");
Serial.println ("");
}
}
//------------------------------------------------------
// Texte HTML à envoyer pour signifier que tout est ok
//-----------------------------------------------------
void requeteOk(EthernetClient client){
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: keep-alive");
client.println();}
//------------------------------------------------------
// Chaine à envoyer au client en réponse à une requête
//-----------------------------------------------------
void envoiReponse(EthernetClient client){
//-- Envoi au client en fonction de l'état d'une broche
if (digitalRead(3)) {client.println("Etat switch 3 : ON");}
else {client.println("Etat switch 3 : OFF");}}
}
La page HTML qui affichera suite à l’appui sur un bouton le message que la carte Arduino aura prévu d’envoyer (ici « Etat du switch 3 »); ATTENTION, pour tester le fonctionnement d’une page AJAX il est nécessaire de la placer dans le répertoire www d’un serveur Apache :
<!DOCTYPE html>
<html>
<head>
<title>AJAX ARDUINO</title>
<script>
function GetSwitchState() {
nocache = "&nocache="+ Math.random() * 1000000;
//-- Défibition d'un objet XHR
var request = new XMLHttpRequest();
//-- Fonction de callback pour gérer la réponse du serveur
request.onreadystatechange = function() {
if (this.readyState == 4) {
if (this.status == 200) {
if (this.responseText != null) {
document.getElementById("switch_txt").innerHTML = this.responseText;}}}}
//-- Définition d'un GET à envoyer au serveur Arduino
request.open("GET", "ajax_switch" + nocache, true); //-- nocache pour compatibilité IE
//-- Envoi du GET au serveur Arduino
request.send(null);
}
</script>
</head>
<body>
<h1>Arduino AJAX Test</h1>
<p id="switch_txt">Etat du switch 3 : inconnu...</p>
<button type="button" onclick="GetSwitchState()">SWITCH 3</button>
</body>
</html>
B) Initialisation en continu de la page HTML avec des valeurs en provenance de capteurs :
Pour réaliser cela la fonction d’affichage des valeurs devra être rafraichie à l’aide de l’instruction setTimeout() et à l’aide de la balise <body onload= »founction()« > comme indiqué dans la page suivante (fichiers ici) :
<!DOCTYPE html>
<html>
<head>
<title>Arduino Analog Page</title>
<script>
function GetSwitchAnalogData() {
nocache = "&nocache=" + Math.random() * 1000000;
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if (this.readyState == 4) {
if (this.status == 200) {
if (this.responseText != null) {
document.getElementById("sw_an_data").innerHTML = this.responseText;}}}}
request.open("GET", "ajax_switch" + nocache, true);
request.send(null);
//-- Rafraichisement de la page toute les secondes (1000 ms)
setTimeout('GetSwitchAnalogData()', 1000);}
</script>
</head>
<body onload="GetSwitchAnalogData()">
<h1>Arduino AJAX Input</h1>
<div id="sw_an_data"></div>
</body>
</html>
Seule la fonction de retour de données est changée dans le sketch Arduino (ici test d’une broche Analogique 2 et d’un switch situé en broche digitale 3 ) :
void envoiReponse(EthernetClient client){
int analog_val;
//-- Lecture état broche 3
if (digitalRead(3)) {
client.println("<p>Switch 3 : ON</p>");}
//-- Lecture de la broche analogique A2
analog_val = analogRead(2);
client.print("<p>Analog A2: ");
client.print(analog_val);
client.println("</p>");}
C) Enchaînement de pages :
Nous aborderons ici l’appel de lien permettant la navigation de page en page. Ainsi le serveur Arduino pourra être constitué d’une page « index » contenant des liens vers n pages.
Pour déclarer un lien vers une page (ici page.htm) il faut utiliser la balise HTML <a> la syntaxe étant :
<a href="page.htm">libellé_lien</a>
Quand un navigateur charge une page, les données reçues par le serveur sont « GET /page.htm » ou « GET / » quand c’est la page index.htm. Ainsi, il suffit d’insérer dans notre code d’analyse de la requête reçue les instructions suivantes :
//-- si pas requête souhaitée on regarde si l'utilisateur a cliqué sur un lien
if (msg.indexOf("GET / ") || StrContains(HTTP_req, "GET /index.htm")){
webFile = SD.open("index.htm");} //-- Envoie de la page Index (par défaut)
else if (msg.indexOf("GET /page2.htm")) {
webFile = SD.open("page.htm");} //-- Envoie de la page du lien cliqué (ici page.htm)
D) Appel d’une page HTML en passant des paramètres :
Ceci peut être utile si l’on souhaite transférer des paramètres d’une page web à une autre.
<!DOCTYPE html>
<html>
<head>
<meta charset=\"utf-8\" />
<title>Test de transfert de paramètre </title>
<script language="JavaScript">
//-- Appel de la page : demo.html?LED1=ON&CAPTEUR1=1234
function processUser()
{ var parameters = location.search.substring(1).split("&"); //-- location.search contient la liste des paramètres (à retirer ? et à éclater selon les '&'
var param1 = parameters[0].split("=");
var param2 = parameters[1].split("=");
alert("1er param : '"+param1[1]+"' 2nd : '"+param2[1]+"'");
}
</script>
<!-- Fin du code Javascript -->
</head>
<body>
<p>
<button onclick="processUser();">Cliquez pour visualiser les parametres...</button>
</p>
</body>
</html>
E) Affichage des images dans une page HTML :
Copiez les images sur la carte SD et placer les images dans la page HTML à l’aide de la balise img : <img src= »pic.jpg » />
Le chargement des images dans le navigateur se fait dès que la page est reçue. A la fin de la réception de la page, le serveur reçoit des commandes GET pour chaque image qu’il doit placer dans la page (exemple GET /pic.jpg
Il convient donc dans le sketch de tester la présence de l’instruction GET /pic.jpg pour envoyer le fichier image au client.
if (msg.indexOf("GET /pic.jpg") > -1) {
Serial.println ("Envoi image !");
webFile = SD.open("pic.jpg");
if (webFile) {while(webFile.available()) {
client.write(webFile.read());}} //-- Envoi de la page web au client
webFile.close();}
F) Protection d’un site par password :
Pour cela, la page d’accueil « acceuil.htm » contiendra un formulaire de saisie de login et password du type :
<!DOCTYPE html>
<html>
<head>
<title>Connexion Arduino privée</title>
</head>
<body>
<TABLE border=0>
<FORM action="cible.htm" method=get>
<TR><TD align='right'>Votre login</TD><TD><INPUT size=12 name='log'></TD></TR>
<TR><TD align='right'>Votre mot de passe</TD><TD><INPUT type=password size=12 name='pass'></TD></TR>
<TR><TD></TD><TD><INPUT type=submit value="Validez" ></TD></TR></TABLE>
</body>
</html>
Cette page appellera, à la validation du formulaire, une autre page « cible.htm » avec comme paramètre le login et le password, soit l’instruction « cible.htm?log=login&pass=pass« . Ces paramètres seront décodés par notre programme arduino pour valider l’affichage d’une 3ième page « protegee.htm » qui sera celle de notre site…
Dans l’exemple ci-dessous, le serveur affiche la page « acceuil.htm » qui demande un login et mot de pass. en fonction du login et de la validité du password affichage d’une page « pageadm.htm » ou « pageuser.htm ». Gestion de la présence d’une image (ici « logo.png »).
//******************************************************************
//*** Affichage de pages suite à la saisie d'un LOGIN et PASSWORD **
//******************************************************************
#include <SPI.h>
#include <Ethernet.h>
#include <SD.h>
//--- Declarations ---
byte mac[] = { 0x00, 0xAD, 0xBE, 0xEF, 0xFE, 0x50 }; //-- Addresse MAC à adapter
IPAddress ip(192,168,0,50); //-- Adresse IP à adapter
EthernetServer serveurHTTP(80); //-- objet serveur utilisant le port 80 = port HTTP
String msg=""; //-- pour réception chaine requete
File webFile; //-- pour afficher la page HTML
//--- Setup ---
void setup() {
Serial.begin(9600);
//-- Initialisation de la carte SD
Serial.println("Initializing SD card...");
if (!SD.begin(4)) {
Serial.println("ERREUR - La carte SD est indisponible!");
return;}
Serial.println("Carte SD OK");
if (!SD.exists("pagepass.htm")) {
Serial.println("ERREUR - Fichier pagepass.html absent !");
return;}
Serial.println("Fichier pagepass.html OK");
//---- initialise le serveur ----
Ethernet.begin(mac, ip);
delay(1000);
Serial.print("Shield Ethernet OK : L'adresse IP du shield Ethernet est : " );
Serial.println(Ethernet.localIP());
serveurHTTP.begin();
Serial.println("Serveur Ethernet OK : Ecoute sur port 80 (http)");
}
//--- Traitement ---
void loop(){
EthernetClient client = serveurHTTP.available(); //-- Objet Client Web
if (client) { //-- Si un navigateur interroge l'URL de la carte Arduino
Serial.println ("... Reception demande client...");
////////////////// Réception requete //////////////////////////
boolean currentLineIsBlank = true;
msg="";
while (client.connected()) {
if (client.available()) { //-- Si octets disponibles en lecture
char c = client.read();
msg+=c;
if (c == '\n' && currentLineIsBlank) { //--Dernière ligne du client reçue est vide
Serial.println ("Reception requete terminee");
//-- Analyse de la requete reçue
Serial.println("------------ Analyse ------------");
requeteOk(client);
//-- La chaine ok de connexion est "cible.htm?log=login&pass=password" où login et password sont les valeurs saisies par l'utilisateur
if (msg.indexOf("log=admin&pass=andro1") > -1) { //-- Si dans la requête récupérée le mot clef admin est inclu
Serial.println ("Mot de pass admin ok !");
//-- envoi de la réponse HTTP ---
envoiReponse(client,"pageadm.htm");}
if (msg.indexOf("GET logo.png") > -1) { //-- Si dans la requête récupérée le mot clef admin est inclu
Serial.println ("Envoi image logo...");
//-- envoi de la réponse HTTP ---
envoiReponse(client,"logo.png");}
if (msg.indexOf("log=user&pass=vero") > -1) { //-- Si dans la requête récupérée le mot clef admin est inclu
Serial.println ("Mot de pass admin ok !");
//-- envoi de la réponse HTTP ---
envoiReponse(client,"pageuser.htm");}
else { //-- si pas la chaine souhaitée on envoie page entiere
Serial.println("Envoi page acceuil");
envoiReponse(client,"acceuil.htm");}
//-- Affichage de la requete HTTP sur le port série
Serial.print(msg);
msg = "";
break;}
//-- Chaque ligne de texte reçue se termine par \r\n
if (c == '\n') { //-- Fin de ligne reçue
currentLineIsBlank = true;}
else if (c != '\r') { //-- Un caractère en provenance du client est reçu
currentLineIsBlank = false;}}}
//-- fermeture de la connexion avec le client après envoi réponse
delay(1);
client.stop();
Serial.println("------------ Fermeture de la connexion avec le client ------------");
Serial.println ("");
}
}
//------------------------------------------------------
// Texte HTML à envoyer pour signifier que tout est ok
//-----------------------------------------------------
void requeteOk(EthernetClient client){
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: keep-alive");
client.println();}
//------------------------------------------------------
// Page à envoyer au client en réponse à une requête
//-----------------------------------------------------
void envoiReponse(EthernetClient client, String nompage){
char filename[nompage.length()+1];
nompage.toCharArray(filename, sizeof(filename));
//-- Envoi au client en fonction de l'état d'une broche
webFile = SD.open(filename);
if (webFile) {while(webFile.available()) {
client.write(webFile.read());}} //-- Envoi de la page web au client
webFile.close();
}
G) Récupération du statut des capteurs et affichage dans un canevas :
Nous utiliserons une page qui affichera dans un canevas le plan d’une maison. Chaque entrée de la maison étant pourvu de capteurs, la page sera alimentée par un fichier XML mis à jour toute les secondes par une carte Arduino MEGA.
La structure du fichier XML est :
<?xml version = "1.0" ?>
<inputs>
<portailcours>OFF</portailcours>
<portecours>OFF</portecours>
<porteentree>OFF</porteentree>
<porteparking>OFF</porteparking>
<portail>OFF</portail>
<portillon>OFF</portillon>
<bal>OFF</bal>
</inputs>
Notre programme Arduino enverra, à la réception du mot clef ‘alarmecontacts’ les lignes du fichier XML…
La structure de la fonction de récupération des informations des capteurs utilise maintenant l’instruction responseXML :
function GetArduinoInputs()
{nocache = "&nocache=" + Math.random() * 1000000;
var request = new XMLHttpRequest();
request.onreadystatechange = function()
{if (this.readyState == 4) {
if (this.status == 200) {
if (this.responseXML != null) {
//-- Extraction des valeurs à partir du fichier XML reçu
dpPortailcours = this.responseXML.getElementsByTagName('portailcours')[0].childNodes[0].nodeValue;
dpPortecours = this.responseXML.getElementsByTagName('portecours')[0].childNodes[0].nodeValue;
dpPorteentree = this.responseXML.getElementsByTagName('porteentree')[0].childNodes[0].nodeValue;
dpPorteparking = this.responseXML.getElementsByTagName('porteparking')[0].childNodes[0].nodeValue;
dpPortail = this.responseXML.getElementsByTagName('portail')[0].childNodes[0].nodeValue;
dpPortillon = this.responseXML.getElementsByTagName('portillon')[0].childNodes[0].nodeValue;
dpBal = this.responseXML.getElementsByTagName('bal')[0].childNodes[0].nodeValue;
}}}}
request.open("GET", "alarmecontacts" + nocache, true);
request.send(null);
setTimeout('GetArduinoInputs()', 1000);}
H) Enregistrement d’un fichier des accès :
La création d’un fichier Log permet de mémoriser les accès aux pages et de tracer des actions effectuées. Dans le cas d’une détection de présence il pourrait aussi enregistrer l’état des capteurs. Par exemple il est possible de gérer un fichier « log.txt » pour les accès et « capteurs.txt » pour l’état des capteurs. Un seul fichier ne pouvant être ouvert à la fois et l’écriture du fichier se faisant à la fermeture du fichier, il suffit de placer le code suivant pour chaque événement devant être mémorisé.
myFile = SD.open("log.txt", FILE_WRITE);
//-- si on peut ouvrir le fichier
if (myFile) {
Serial.print("Enregistrement Log");
myFile.println(dDateAcces,sProfil,sUser);
//-- Ecriture et fermeture du fichier
myFile.close();
} else {
//-- erreur avecle fichier
Serial.println("error opening test.txt");
}
Remarque pour récupérer la date et heure de connexion il est nécessaire d’ajouter un module tel que le DS1307 pour récupérer des paramètres temporels.
Bonjour,
J’ai essayé de suivre votre tutoriel ainsi que celui « http://startingelectronics.org/tutorials/arduino/ethernet-shield-web-server-tutorial/SD-card-AJAX-web-server/ » mais à chaque fois le téléversement se déroule normalement et le moniteur série indique que le document html a bien été trouvé mais la page du navigateur safari et Firefox) reste désespérément vierge. Quand vous précisez : »ATTENTION, pour tester le fonctionnement d’une page AJAX il est nécessaire de la placer dans le répertoire www d’un serveur Apache : » le serveur doit être installé sur l’ordinateur qui est utilisé pour visualiser la page ou bien sur l’arduino ?
Merci de votre aide.
Cal
Bonjour,
Je vous confirme que la page doit être placée sur un serveur Apache.Vous pouvez utiliser un serveur en local pour vos tests…
Bonsoir,
Merci de votre réponse. Mais j’avais compris que je devais mettre la page sur la carte SD alors qu’est-ce que je met sur mon serveur apache ? (j’ai installé Mamp).
Cordialement
Cal