Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Le navigateur aux commandes de l'Arduino - Partie 3 : programmation côté client en JavaScript,
Un tutoriel d'Hervé Troadec

Le , par naute

0PARTAGES

8  0 
Bonjour à tous .

J'ai le plaisir de vous présenter le troisième et dernier volet de mon tutoriel consacré au pilotage d'un Arduino depuis un navigateur :




Après nous être consacrés dans les première et deuxième parties à la programmation côté serveur, je vous propose, dans cette troisième et dernière partie, de nous intéresser à la programmation côté client avec JavaScript qui va nous permettre de manipuler le DOM, AJAX et JSON.
Le navigateur aux commandes de l'Arduino
Le navigateur aux commandes de l'Arduino - 2

Vous en souhaitant bonne lecture,
amicalement,
naute

Retrouvez les meilleurs cours et tutoriels pour apprendre Arduino

Une erreur dans cette actualité ? Signalez-le nous !

Avatar de electroremy
Membre actif https://www.developpez.com
Le 25/06/2020 à 14:38
Bonjour,

Ce tutoriel est génial

Bien vu, le lien vers le clone Arduino UNO avec Ethernet intégré

Je travaille sur quelque chose de très similaire : des arduinos UNO reliés entre eux par Ethernet, mais avec en plus un écran TFT tactile sur chaque Arduino

J'en ai beaucoup bavé mais ça fonctionne : https://www.developpez.net/forums/d2...s-arduino-uno/

Une fusion entre ton tutoriel et mon projet aboutit à quelque chose de vraiment versatile : autant de modules que l'on veux avec chacun un bel écran couleur tactile, avec en plus un pilotage possible via un navigateur depuis un smartphone ou un ordinateur.

Et comme tout fonctionne en câblage ethernet sur un réseau local (pas besoin de connectivité Internet et le WIFI peut être limité à ce qui est nécessaire avec un NANO ESP comme passerelle) on a un système fiable, immunisé contre les interférences et le piratage.

A bientôt
3  0 
Avatar de Jay M
Membre expérimenté https://www.developpez.com
Le 20/06/2020 à 19:56
Bravo pour ce 3ème volet très didactique !

une petite faute de frappe dans
la procédure setAttribut("attribut", "valeur" sert à modifier la valeur d’un attribut. Ici, nous travaillons sur l’attribut value de bouton que nous fixons à ON ou à OFF selon que son action sera d’allumer ou d’éteindre la LED ;
il s'agit bien sûr de setAttribute() comme vous l'utilisez ensuite

éventuellement une petite suggestion:

La valeur 4 pour xhr.readyState signifie que l'opération de récupération est terminée. Cela peut signifier que le transfert de données s'est terminé avec succès ou a échoué. Donc pour être sûr que tout s'est bien passé on peut aussi tester le status (cf la liste des réponses possibles, 200 veut dire 'OK')
Code : Sélectionner tout
1
2
3
4
5
6
  xhr.onreadystatechange = function()
  {
    if (xhr.readyState == 4 && xhr.status == 200) {
      actualisation(xhr.responseText);
    }
  }

Petite note au passage: on recommande souvent d'éviter les "nombres magiques" quand on programme et donc parfois au lieu de tester La propriété readyState avec sa valeur "en dur" (4) on voit du code qui compare avec XMLHttpRequest.DONE
ça donnerait cela:
Code : Sélectionner tout
1
2
3
4
5
6
  xhr.onreadystatechange = function()
  {
    if (xhr.readyState == XMLHttpRequest.DONE) {
      actualisation(xhr.responseText);
    }
  }
Cependant Internet Explorer jusqu'il y a peu n'appellait pas cela DONE mais READYSTATE_COMPLETE mais la valeur est bien 4. Donc en mettant 4 on met plus de chances de son côté que ça fonctionne partout...

PS1:
(pas de commentaires désobligeants quant à mon sens aigu de l’esthétique ;-)
Je ne vois pas... à moins que vous ne disiez cela parce que vous avez oublié de fermer la parenthèse sur cette phrase ??

PS2: la duplication de l'instance EthernetClient plutôt que son passage par référence est vraiment à éviter car la classe sous-jacente gère un nombre limité de sockets (donc ça pourrait coincer suivant l'activité du serveur) et vous vous retrouvez avec des pointeurs partagés sur des buffers mémoire et des variables qui se désynchronisent.

PS3: je suppose qu'avec ce bout de code vous espérez échapper à un Stack Overflow ?
Code : Sélectionner tout
1
2
3
4
5
6
       char reception[127];
      memset(reception, 0, 127);
      if (reception == NULL)
      {
        while(1) {delay(1000);}
      }
en fait reception est alloué sur la pile et son pointeur sera jamais NULL (et vous avez mis les 127 octets de mémoire à zéro avant de tester le pointeur!) et si la place n'est pas dispo, vous n'obtiendrez pas un pointeur null mais votre arduino va crasher direct au moment de la demande des 127 octets sur la pile... Comme votre app va vraiment souvent utiliser le web, le plus simple est de prendre un buffer en variable globale, comme cela le compilateur réservera la mémoire nécessaire. 127 est aussi un peu grand pour l'usage prévu dans votre cas, vous pourriez le réduire significativement.

PS4: votre code pour l'analyse des chaînes est vraiment complexe, vous codez des fonctions qui existent dans les bibliothèques standard (et sont déjà présentes dans votre binaire sans doute).

-> la longueur d'une cString (ce que vous faites avec dimChaine()) s'obtient par appel à strlen()

-> concaténer deux cString (ce que vous faites avec ajoute()) s'obtient par appel à strcat()

-> comparer deux cString se fait avec strcmp()

-> vous pourriez simplement utiliser strstr() pour voir si la chaîne reçue contient le mot clé ajax.

-> Si vous voulez vraiment voir si ce mot clé est à un endroit bien défini sur un certain nombre de caractères, pas la peine de décaler comme vous le faites tous les caractères dans le buffer (je suppose pour virer le "GET /" il suffit de faire un strncmp() avec comme pointeur vers la chaine source reception+5

=> il y a plein de fonctions sur les cString dans stdlib.h et string.h
2  0 
Avatar de Vincent PETIT
Modérateur https://www.developpez.com
Le 22/06/2020 à 12:35
Un grand
2  0 
Avatar de Jay M
Membre expérimenté https://www.developpez.com
Le 23/06/2020 à 12:57
Salut

Citation Envoyé par naute  Voir le message
Il semblerait . En même temps, si c'est la seule...

Oui y'avait clairement eu du boulot de relecture avant. ça permet d'être un peu plus parfait : )

Citation Envoyé par naute  Voir le message
Le couple de tests classique est effectivement :
Code javascript : Sélectionner tout
(xhr.readyState == 4 && xhr.status == 200)
Ce n'est pas un mystère.
Pourquoi ai-je déchiré ce couple ? eh bien ! dans notre cas, le second membre ne servira pas à grand chose. ... Par contre, rien n'empêche l'utilisateur d'implémenter ce traitement si son application personnelle le nécessite.

C'était ce que je sous entendais: comme c'est un tuto - vous pourriez mentionner que readyState == 4 n'est pas suffisant mais que dans votre cas pas la peine de chercher plus loin.

Citation Envoyé par naute  Voir le message
Je ne sais pas si on recommande vraiment de ne pas utiliser les "nombres magiques", mais ils sont pourtant présents dans tous les beaucoup de codes

C'est une bonne pratique. vous trouverez cela dans tous les bons bouquins de programmation, quel que soit le langage. Dans ma vision d'un tuto, c'est pas mal de mettre en avant de bonnes habitudes et définir ces nombres magiques avec une #define ou mieux avec un const ou un constexpr, ça facilite la lecture quand on revient voir son code 6 mois plus tard et qu'on n'a pas le tuto sous la main.

c'est pas nouveau cf Wikipedia:
Constantes numériques non nommées
Le terme de magic number peut également correspondre à l'utilisation de constantes numériques non nommées dans le code source d'un programme. L'utilisation de ces constantes viole les anciennes règles de programmation issues de COBOL, de FORTRAN et de PL/I9, ne rend pas plus clair le choix de cette valeur et provoque généralement des erreurs de programmation. Selon certains, le nommage de toutes les constantes rend le code plus lisible, plus compréhensible et plus facilement maintenable.

Citation Envoyé par naute  Voir le message
Le chiffre "4" peut être considéré "magique" quand on ne connait pas sa signification. Dans notre cas, sa signification est explicitée quelques lignes plus haut, avec les autres valeurs que peut prendre le paramètre xhr.readyState. Il n'y a donc plus de magie. De plus, en toute rigueur, vous auriez donc dû mettre xhr.statusText == "OK" au lieu de xhr.status == 200 pour être en accord avec votre remarque.

Oui 200 est aussi un nombre magique. Mon explication était plutôt là pour exprimer le fait que bien qu'en général on évite les nombres magiques, le manque de standardisation généralisée sur les noms associés aux nombres magiques pour AJAX fait qu'il est plus sûr d'utiliser le nombre magique directement. Donc dans certains cas il ne faut pas le faire et on peut expliquer pourquoi on ne le fait pas ==> j'y vois là un valeur éducative

Ce n'est pas un oubli. C'est mon choix typographique (voir ci-dessus). Je trouve que "))" n'est pas très esthétique (dans une phrase du moins : dans du code, c'est standard). C'est évidemment tout à fait personnel. Je faisais bien entendu allusion à l'esthétique très basique de mon exemple .

c'était un peu d'humour pour dire que je trouvais le reste tout à fait bien

En fait, d'après ce que j'avais cru comprendre dans un tutoriel C que j'ai étudié, quand une demande d'allocation n'aboutissait pas, notamment par manque de place, un pointeur "NULL" était renvoyé : le but de ce test était donc en théorie de stopper le programme dans ce cas.

c'est vrai pour l'allocation dynamique avec malloc() et ses dérivés ou l'instanciation, mais quand c'est sur la pile directement vous vous prenez un "Stack Overflow" direct. Dans les plateformes un peu avancées, on pourrait jouer avec du try/catch mais pour faire cela il faut plein de mémoire et justement ici on en manque... (tel que c'est écrit vous essayez de mettre à 0 la mémoire avant de tester si le pointeur est OK, donc c'est quand même un souci).

Complexe, mon code ?! Vous me flattez . Non, n'exagérons rien. Peut-être un peu lourd et on peut évidemment s'en passer. Seulement voilà.
Je ne connais pas bien le C. Si l'EDI Arduino utilisait le Pascal (dans lequel, d'ailleurs, le traitement des chaînes de caractère est très différent) je serais au paradis, mais ce n'est pas le cas. Je me suis donc mis au C, sans forcément beaucoup d'enthousiasme, pour pouvoir "travailler" avec cette plateforme, et je suis bien certain de ne pas être le seul. Mais bon ! la programmation reste de la programmation, quelque soit le langage utilisé. Il faut s'adapter. Je n'ai rien contre le C, mais sa syntaxe est radicalement différente de celle du Pascal, et c'est un changement de paradigme délicat à intégrer. Par contre, sa similitude avec celle du JavaScript est un avantage quand on utilise les deux dans une même réalisation, comme ici.

Je me doutais bien que ces fonctions existaient, mais j'ai estimé plus rapide de les écrire que de les chercher.

je comprends et me doutais que c'était la raison. Là encore, mon point de vue c'est que dans un tuto qui a un objectif pédagogique, il est bon d'encourager les bonnes pratiques générales.

Je vous propose de remplacer - si vous le voulez bien - votre code (qui génère pas mal de warnings à la compilation) par celui ci, plus simple et utilisant la fonction strstr() qui recherche une sous-chaîne dans une chaîne. (je ne l'ai pas testé, c'est la traduction simplement de votre code). La seule optimisation importante effectuée c'est la génération du JSON à la volée plutôt que de construire un buffer inutile (et quelques modifications sur les noms des constantes, A0 au lieu de 0 par exemple, ou des définitions en const byte partout pour les pins et éviter une variable globale 'i' qui n'a aucun sens à être globale).

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
//  *** LA GESTION DE LA CARTE SD *** 
#include <SdFat.h> 
SdFat carteSd; 
const byte csPinSD = 4; 
 
// *** LA GESTION ETHERNET *** 
#include <Ethernet.h> 
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};  // notre adresse MAC 
IPAddress ip(192, 168, 1, 200); // notre adresse IP, à adapter à votre réseau local 
EthernetServer serveurHTTP(80); // 80 est le port standard du protocole HTTP 
 
const byte tailleMaxGETBuffer = 60; // taille max pour la commande GET reçue, à adapter aux besoins 
 
//  *** LES PINS UTILISÉES *** 
const byte ledPin = 2;    // une LED sur la broche numérique 2 
const byte LM35Pin = A0;  // le capteur de température sur la broche analogique 0 
 
// *** LES FONCTIONS UTILITAIRES *** 
 
void arHtml(EthernetClient& nomClient, const __FlashStringHelper* type) // on passe le type en utilisant la macro F("...") pour économiser la mémoire SRAM 
{ 
  nomClient.println(F("HTTP/1.1 200 OK")); 
  nomClient.print(F("Content-Type: ")); 
  nomClient.println(type); 
  nomClient.println(F("Connection: close")); 
  nomClient.println(); 
} 
 
void envoiFichier(EthernetClient& nomClient, const char* fichierEnCours) 
{ 
  if (carteSd.exists(fichierEnCours)) 
  { 
    File fichier = carteSd.open(fichierEnCours, FILE_READ); 
    if (fichier) { // si l'accès s'est bien passé 
      while (fichier.available()) nomClient.write(fichier.read()); // on envoie le contenu du fichier 
      fichier.close();  // puis on ferme le fichier 
    } 
  } 
} 
 
float temperature(byte broche, word iteration) 
{ 
  long lecture = 0; 
  for (word i = 0; i < iteration; i++) 
  { 
    lecture += analogRead(broche); 
  } 
  return (float(lecture) * 500.0 / 1024.0 / iteration); 
} 
 
 
void maj(EthernetClient& nomClient) 
{ 
  float temperatureMoyenne = temperature(LM35Pin, 10);  // on calcule la moyenne de la température sur quelques échantillons 
 
  // on va renvoyer un JSON 
  arHtml(nomClient, F("application/json")); 
 
  // on construit et emet notre JSON 
  nomClient.print(F("{\"bouton\":\"")); 
  if (digitalRead(ledPin) == HIGH) 
    nomClient.print(F("ledOn")); 
  else 
    nomClient.print(F("ledOff")); 
  nomClient.print(F("\",\"temperature\":")); 
  nomClient.print(temperatureMoyenne, 2); // on emet la température avec 2 chiffres après la virgule 
  nomClient.println(F("}")); 
} 
 
// *** LE PROGRAMME PRINCIPAL *** 
 
void setup() 
{ 
  pinMode(ledPin, OUTPUT); 
  Serial.begin(115200); 
  carteSd.begin(csPinSD); 
  Ethernet.begin(mac, ip); 
} 
 
void loop() 
{ 
  EthernetClient client = serveurHTTP.available(); 
  if (client) 
  { 
    if (client.connected()) 
    { 
      char reception[tailleMaxGETBuffer + 1]; // +1 car une chaîne bien formée se termine par un '\0' invisible 
 
      byte nbCar = 0; 
      while (client.available())       // tant que des données sont disponibles 
      { 
        char carLu = client.read();     // on lit le prochain octet 
        if (carLu != '\n')              // si ce n'est pas la marque de fin de ligne 
        { 
          if (nbCar < tailleMaxGETBuffer) reception[nbCar++] = carLu; // et si on a la place alors on conserve le caractère sinon on l'ignore 
        } 
        else break;                     // si on a reçu la fin de ligne on termine 
      } 
      reception[nbCar] = '\0';          // on met la marque de fin de notre chaîne correctement 
 
      // on analyse maintenant ce que l'on a reçu en cherchant des mots clés en utilisant strstr() (cf http://www.cplusplus.com/reference/cstring/strstr/) 
 
      // est-ce une demande de type ajax ? 
      char *ajaxPtr = strstr(reception, "ajax?"); 
      if (ajaxPtr != NULL) 
      { 
        if (strstr(ajaxPtr, "ON") != NULL)  digitalWrite(ledPin, HIGH);       // si la requête demande d'allumer, on le fait 
        else if (strstr(ajaxPtr, "OFF") != NULL) digitalWrite(ledPin, LOW);   // si la requête demande l'extinction, on le fait 
        // puis on envoie une réponse JSON avec mise à jour 
        maj(client); 
      } 
 
      // est-ce une demande pour telecommande.css 
      else if (strstr(reception, "telecommande.css") != NULL) 
      { 
        arHtml(client, F("text/css")); 
        envoiFichier(client, "telecommande.css"); 
      } 
 
      // est-ce une demande pour telecommande.js 
      else if (strstr(reception, "telecommande.js") != NULL) 
      { 
        arHtml(client, F("application/javascript")); 
        envoiFichier(client, "telecommande.js"); 
      } 
 
      // est-ce une demande pour icone.png 
      else if (strstr(reception, "icone.png") != NULL) 
      { 
        arHtml(client, F("image/png")); 
        envoiFichier(client, "icone.png"); 
      } 
 
      // sinon on envoie la page standard 
      else 
      { 
        arHtml(client, F("text/html")); 
        envoiFichier(client, "telecommande.html"); 
      } 
 
      delay(1); 
    } 
    client.stop();  // on se déconnecte du serveur. le buffer non lu est conservé jusqu'à disparition de l'instance client, ce qui se fait en fin de loop 
  } 
}
Cela dit, pour mon usage personnel, j'utilise la classe String qui me convient très bien et qui ne m'a jamais causé de misère. Et comme pour les "nombres magiques", si elle existe, c'est bien pour être utilisée. Mais chacun a le droit d'avoir son opinion et ses habitudes.

Oui et dans de très nombreux cas la classe String ne créera pas de souci (Son seul problème vient que le jour où vous en aurez un, ce sera une grosse galère à débuger). Pour des petits programmes qui ne tournent pas pendant des mois ou qui font une usage raisonné de la classe (allocation avec reserve d'espace minimum, dé-allocation dans l'ordre inverse des allocations, ..) alors vous n'aurez aucun problème. Ma recommandation pour partager les bonnes pratiques serait simplement d'appeler reserve() juste après sa définition quand on sait que la String va grossir (reception) pour éviter le morcellement mémoire. ça permettrait aussi de tester si la mémoire a pu être allouée (même si le doc dit que la méthode ne retourne rien, elle répond en fait 0 ou 1 en fonction de si ça a marché ou pas).

Donc avec la classe String ça donnerait un truc comme cela:

Code : Sélectionner tout
1
2
3
4
5
6
7
const byte tailleMaxGETBuffer = 60; // taille max pour la commande GET reçue, à adapter aux besoins 
... 
  String reception; 
  if (reception.reserve(tailleMaxGETBuffer))  // si on a pu réserver la mémoire nécessaire  
  { 
    .... 
  }
La richesse est dans la diversité.

Tout à fait et pas de souci si vous ne souhaitez ne pas en tenir compte. C'est votre tuto !! Ce sont juste des remarques que j'espère constructives, pour encourager la prise de bonnes habitudes pour ceux qui liront votre tuto.
2  0 
Avatar de Jay M
Membre expérimenté https://www.developpez.com
Le 25/06/2020 à 11:43
Salut

Citation Envoyé par naute Voir le message
Par contre, il serait plus judicieux de pouvoir faire ces mises au point en amont, avant publication (petit appel du pied ).
Certes ! Si on me demande, sur des sujets que je maitrise et que j'ai le temps, je veux bien

Pour la variable 'i' ça ne mange pas de pain de la déclarer dans les fonctions ou directement dans la boucle for quand il y en a. Vous avez fait attention au fait que les fonctions n'étaient pas interdépendantes mais les débutants n'auront peut-être pas ce réflexe. Il vaut mieux mettre en global ce que l'on veut partager entre toutes les fonctions et laisser en local le reste.

Pour la chaîne JSON, c'est effectivement un choix. Comme on essaye de minimiser l'impact mémoire et qu'on n'a pas besoin d'avoir la chaîne en mémoire, d'un point de vue émission ça ne change rien si on l'envoie par petits bouts.

Sinon pour la construire on peut utiliser un buffer (local) puis strcpy(), strcat() et dtostrf() (attention à a taille du buffer) ou alors sprintf() (qui consommera un peu plus de mémoire car c'est une grosse fonction). Si ça vous intéresse de mettre cette alternative, voici 3 variantes (bien sûr il ne faudra en conserver qu'une !)

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
void maj(EthernetClient& nomClient)
{
  float temperatureMoyenne = temperature(LM35Pin, 10);  // on calcule la moyenne de la température sur quelques échantillons

  // on va renvoyer un JSON
  arHtml(nomClient, F("application/json"));

  // on construit et emet notre JSON par petits bouts
  // avantage: pas de buffer, on peut conserver les chaînes en mémoire flash ce qui économise la SRAM
  nomClient.print(F("{\"bouton\":\""));
  if (digitalRead(ledPin) == HIGH)
    nomClient.print(F("ledOn"));
  else
    nomClient.print(F("ledOff"));
  nomClient.print(F("\",\"temperature\":"));
  nomClient.print(temperatureMoyenne, 2); // on emet la température avec 2 chiffres après la virgule
  nomClient.println(F("}"));

  /* solutions alternatives: on construit le JSON dans un buffer */
  const byte tailleMaxJSONBuffer = 50;
  char JSONBuffer[tailleMaxJSONBuffer]; // s'assurer qu'on a la place nécessaire

  /*version alternative #1: on utilise sprintf.
     inconvénients:
      - on a besoin d'un buffer temporaire
      - les chaînes ne seront pas en mémoire flash et prendront de la SRAM
      - sprintf est une grosse fonction qui prendra de la place en mémoire programme
  */
  sprintf(JSONBuffer, "{\"bouton\":\"%s\",\"temperature\":%.2f}", (digitalRead(ledPin) == HIGH) ? "ledOn" : "ledOff", (double) temperatureMoyenne);
  nomClient.println(JSONBuffer);

  /* solution alternative #2: on remplit le buffer un peu plus "à la main".
    inconvénients:
    - on a besoin d'un buffer temporaire
    - les chaînes ne seront pas en mémoire flash et prendront de la SRAM
    - les fonctions strcpy, strcat, dtostr occupent un peu de mémoire programme mais sont relativement peu couteuses et sans doute déjà utilisées ailleurs dans les bibliothèques
  */
  strcpy(JSONBuffer, "{\"bouton\":\"");
  if (digitalRead(ledPin) == HIGH)
    strcat(JSONBuffer, "ledOn");
  else
    strcat(JSONBuffer, "ledOff");
  strcat(JSONBuffer, "\",\"temperature\":");
  char* finDeChaine1 = JSONBuffer + strlen(JSONBuffer); // on cherche la fin, finDeChaine pointe sur le '\0' final
  dtostrf((double) temperatureMoyenne, 5, 2, finDeChaine1); // on concatène la conversion de la valeur au bon endroit
  strcat(JSONBuffer, "}");
  nomClient.println(JSONBuffer);

  /* solution alternative #3: on remplit le buffer un peu plus "à la main" et on utilise des chaînes en mémoire programme
    inconvénients:
    - on a besoin d'un buffer temporaire
    - c'est assez lourd à écrire
    - les fonctions strcpy_P, strcat_P sont spécifique au monde AVR. elles occupent un peu de mémoire programme mais sont relativement peu couteuses et sans doute déjà utilisées ailleurs dans les bibliothèques
  */
  const char jsonStart[]  PROGMEM = "{\"bouton\":\"";
  const char jsonLedON[]  PROGMEM =  "ledOn";
  const char jsonLedOFF[] PROGMEM =  "ledOff";
  const char jsonMiddle[] PROGMEM =  "\",\"temperature\":";
  const char jsonEnd[]    PROGMEM =  "}";

  strcpy_P(JSONBuffer, jsonStart);
  if (digitalRead(ledPin) == HIGH)
    strcat_P(JSONBuffer, jsonLedON);
  else
    strcat_P(JSONBuffer, jsonLedOFF);
  strcat_P(JSONBuffer, jsonMiddle);
  char* finDeChaine2 = JSONBuffer + strlen(JSONBuffer); // on cherche la fin, finDeChaine pointe sur le '\0' final
  dtostrf((double) temperatureMoyenne, 5, 2, finDeChaine2); // on concatène la conversion de la valeur au bon endroit
  strcat_P(JSONBuffer, jsonEnd);
  nomClient.println(JSONBuffer);

}
2  0 
Avatar de f-leb
Responsable Arduino et Systèmes Embarqués https://www.developpez.com
Le 26/06/2020 à 20:44
Moi j'adore cette série d'articles parce que c'est très progressif. Je ne suis pas familier avec html, css, js et ajax donc il faut y aller doucement.

Et ceux qui auront assimilé pourront facilement transposer s'ils utilisent d'autres cartes, comme des ESP8266/ESP32, avec d'autres bibliothèques.

2  0 
Avatar de Delias
Responsable Modération https://www.developpez.com
Le 26/06/2020 à 22:07
Bonsoir Naute

Un grand pour ce travail et les tutos.

Citation Envoyé par naute Voir le message
Je ne connais pas bien le C. Si l'EDI Arduino utilisait le Pascal (dans lequel, d'ailleurs, le traitement des chaînes de caractère est très différent) je serais au paradis, mais ce n'est pas le cas.
Apparemment FreePascal peut compiler pour AVR et serait donc compatible avec les Arduino utilisant cette architecture. Mais cela implique de renoncer (du moins partiellement) aux librairies Arduino, et puis c'est encore en bêta.
Un mal pour un bien , mais j'ai bien envie d'essayer.

Bonne suite

Delias
2  0 
Avatar de f-leb
Responsable Arduino et Systèmes Embarqués https://www.developpez.com
Le 02/07/2020 à 23:17


Hervé, j'ai repris le tuto et tes codes pour l'adapter à un DevKit ESP32


Une LED simule l'ouverture/fermeture de la serre, et la température est mesurée avec une sonde DS18B20.

Le serveur est géré avec la bibliothèque ESPASyncWebServer (requêtes asynchrones). Les fichiers html, css, js et images sont en mémoire Flash grâce au système de fichiers SPIFFS.

J'ouvrirai une discussion pour détailler tout ça

Edit : la discussion ouverte ici
2  0 
Avatar de Jay M
Membre expérimenté https://www.developpez.com
Le 03/07/2020 à 10:08
Citation Envoyé par f-leb Voir le message
Hervé, j'ai repris le tuto et tes codes pour l'adapter à un DevKit ESP32
JOLI !
2  0 
Avatar de naute
Rédacteur https://www.developpez.com
Le 22/06/2020 à 11:24
Bonjour .

Citation Envoyé par Jay M  Voir le message
Bravo pour ce 3ème volet très didactique !

Merci .

Citation Envoyé par Jay M  Voir le message
une petite faute de frappe dans ...

Il semblerait . En même temps, si c'est la seule...

Citation Envoyé par Jay M  Voir le message
éventuellement une petite suggestion:...

Le couple de tests classique est effectivement :
Code javascript : Sélectionner tout
(xhr.readyState == 4 && xhr.status == 200)
Ce n'est pas un mystère.
Pourquoi ai-je déchiré ce couple ? eh bien ! dans notre cas, le second membre ne servira pas à grand chose. Il ne s'agit pas ici d'un serveur Apache destiné à faire tourner le site web de la NASA (si tant est que la NASA utilise un serveur Apache). J'ai déjà eu l’occasion de le dire, et je le répète, nous sommes sur un Arduino UNO, dans un réseau local privé, pour des applications non critiques. On n'est pas obligé de sortir l'artillerie lourde, et c'est d’ailleurs pourquoi j'ai simplifié l'utilisation de XHR qui se fait très souvent par l'intermédiaire d'une fonction de callBack dont le mécanisme n'est pas toujours très facile à comprendre.
Donc les deux points qui font que je n'utilise pas ce test sont :
1 un paquet envoyé d'un poste à un autre sur un réseau local privé a bien peu de risque de ne pas arriver ;
2 s'il n'arrive pas, c'est-à-dire si xhr.status != 200, il ne se passera rien et la chose la plus simple à faire est d'attendre l'arrivée du paquet suivant qui va suivre à une seconde d'intervalle. Faire ce test n'est intéressant que s'il est utile de traiter un éventuel résultat négatif, ce qui est superflu ici. Par contre, rien n'empêche l'utilisateur d'implémenter ce traitement si son application personnelle le nécessite.

Citation Envoyé par Jay M  Voir le message
Petite note au passage:...

Le chiffre "4" peut être considéré "magique" quand on ne connait pas sa signification. Dans notre cas, sa signification est explicitée quelques lignes plus haut, avec les autres valeurs que peut prendre le paramètre xhr.readyState. Il n'y a donc plus de magie. De plus, en toute rigueur, vous auriez donc dû mettre xhr.statusText == "OK" au lieu de xhr.status == 200 pour être en accord avec votre remarque.
Je ne sais pas si on recommande vraiment de ne pas utiliser les "nombres magiques", mais ils sont pourtant présents dans tous les beaucoup de codes, ne serait-ce que dans ce test AJAX que j'ai toujours vu écrit comme ça. S'ils existent, c'est bien pour être utilisés. Il y a bien d'autres raccourcis de codage, dans le langage C notamment, qui me semblent bien plus magiques et incompréhensibles aux non initiés (dont je suis). Le tout est de savoir ce qu'on fait (et ce n'est pas toujours facile ;-).

Citation Envoyé par Jay M  Voir le message
PS1:

Ce n'est pas un oubli. C'est mon choix typographique (voir ci-dessus). Je trouve que "))" n'est pas très esthétique (dans une phrase du moins : dans du code, c'est standard). C'est évidemment tout à fait personnel. Je faisais bien entendu allusion à l'esthétique très basique de mon exemple .

Citation Envoyé par Jay M  Voir le message
PS2:

Dont acte !

Citation Envoyé par Jay M  Voir le message
PS3:

En fait, d'après ce que j'avais cru comprendre dans un tutoriel C que j'ai étudié, quand une demande d'allocation n'aboutissait pas, notamment par manque de place, un pointeur "NULL" était renvoyé : le but de ce test était donc en théorie de stopper le programme dans ce cas.
Pour la dimension du tableau (127), je précise bien dans le chapitre VIII. Conclusion qu'elle peut être revue à la baisse en fonction des besoins.

Citation Envoyé par Jay M  Voir le message
PS4:

Complexe, mon code ?! Vous me flattez . Non, n'exagérons rien. Peut-être un peu lourd et on peut évidemment s'en passer. Seulement voilà.
Je ne connais pas bien le C. Si l'EDI Arduino utilisait le Pascal (dans lequel, d'ailleurs, le traitement des chaînes de caractère est très différent) je serais au paradis, mais ce n'est pas le cas. Je me suis donc mis au C, sans forcément beaucoup d'enthousiasme, pour pouvoir "travailler" avec cette plateforme, et je suis bien certain de ne pas être le seul. Mais bon ! la programmation reste de la programmation, quelque soit le langage utilisé. Il faut s'adapter. Je n'ai rien contre le C, mais sa syntaxe est radicalement différente de celle du Pascal, et c'est un changement de paradigme délicat à intégrer. Par contre, sa similitude avec celle du JavaScript est un avantage quand on utilise les deux dans une même réalisation, comme ici.

Je me doutais bien que ces fonctions existaient, mais j'ai estimé plus rapide de les écrire que de les chercher. Cela dit, pour mon usage personnel, j'utilise la classe String qui me convient très bien et qui ne m'a jamais causé de misère. Et comme pour les "nombres magiques", si elle existe, c'est bien pour être utilisée. Mais chacun a le droit d'avoir son opinion et ses habitudes. La richesse est dans la diversité.

Merci pour votre intérêt et vos commentaires ,
amicalement,
naute
1  0