Le navigateur aux commandes de l'Arduino - Partie 3 : programmation côté client en JavaScript,
Un tutoriel d'Hervé Troadec
Le 2020-06-20 17:02:58, par naute, Rédacteur
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
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.
Vous en souhaitant bonne lecture,
amicalement,
naute
-
electroremyMembre éprouvé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ôtle 25/06/2020 à 14:38 -
Jay MExpert confirméBravo pour ce 3ème volet très didactique !
une petite faute de frappe dansla 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 ;
é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 : 1
2
3
4
5
6xhr.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 : 1
2
3
4
5
6xhr.onreadystatechange = function() { if (xhr.readyState == XMLHttpRequest.DONE) { actualisation(xhr.responseText); } }
PS1:(pas de commentaires désobligeants quant à mon sens aigu de l’esthétique ;-)
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 : 1
2
3
4
5
6char reception[127]; memset(reception, 0, 127); if (reception == NULL) { while(1) {delay(1000);} }
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.hle 20/06/2020 à 19:56 -
Vincent PETITModérateurUn grandle 22/06/2020 à 12:35
-
Jay MExpert confirméSalut
Oui y'avait clairement eu du boulot de relecture avant. ça permet d'être un peu plus parfait : )
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.
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.
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.
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.
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 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 : 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.
Donc avec la classe String ça donnerait un truc comme cela:Code : 1
2
3
4
5
6
7const 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é.
le 23/06/2020 à 12:57 -
Jay MExpert confirméSalut
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 : 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
72void 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); }
le 25/06/2020 à 11:43 -
f-lebResponsable Arduino et Systèmes EmbarquésMoi 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.le 26/06/2020 à 20:44 -
DeliasModérateurBonsoir Naute
Un grandpour ce travail et les tutos.
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
Deliasle 26/06/2020 à 22:07 -
f-lebResponsable Arduino et Systèmes Embarqués
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 icile 02/07/2020 à 23:17 -
Jay MExpert confirméle 03/07/2020 à 10:08
-
nauteRédacteurBonjour
.
Merci.
Il semblerait. En même temps, si c'est la seule...
Le couple de tests classique est effectivement :
Code javascript : (xhr.readyState == 4 && xhr.status == 200)
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.
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 danstous lesbeaucoup 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 ;-).
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.
Dont acte!
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.
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,
nautele 22/06/2020 à 11:24