1. envoyer des commandes AT
Dit comme cela, ça paraît simple, mais pour envoyer correctement une commande AT il faut réaliser un certain nombre de tâches. écrire bien sûr sur le port série, mais surtout écouter si la réponse contient des éléments qui nous intéressent.
Comme vous le savez si vous avez lu mon tuto référencé plus haut, il vaut mieux éviter la classe String sur notre petit micro contrôleur et utiliser des fonctions de plus bas niveau standard en C (
stdlib.h et string.h par exemple)
Certaines fonctions d'analyse sont performantes et générales comme sscanf()
mais peuvent être couteuses en mémoire programme.
imaginons(par hasard) que vous ayez une chaîne de caractère dans un buffer qui contient ceci "+IPD,5,200:GET /tup HTTP/1.1" et que vous vouliez extraire le 5 et le /tup.
Un premier code pourrait être celui ci, utilisant la fonction sscanf()
char ligne[] = "+IPD,5,200:GET /tup HTTP/1.1";
int linkID;
int reqSize;
const byte maxURLLength = 20;
char urlBuffer[maxURLLength];
void setup() {
Serial.begin(115200);
if (sscanf(ligne, "+IPD,%d,%d:GET %s HTTP/1.1", &linkID, &reqSize, urlBuffer) == 3) {
Serial.println(linkID);
Serial.println(urlBuffer);
} else {
Serial.println(F("Erreur"));
}
}
void loop() {}
(si vous l'exécutez vous verrez qu'il affiche bien le 5 et le /tup)
et si vous regardez les infos de compilation
Le croquis utilise 3940 octets (1%) de l'espace de stockage de programmes. Le maximum est de 253952 octets.
Les variables globales utilisent 268 octets (3%) de mémoire dynamique, ce qui laisse 7924 octets pour les variables locales. Le maximum est de 8192 octets.
Si vous comparez maintenant avec le programme suivant qui effectue la même chose en gérant des pointeurs et de la recherche dans des chaînes
char ligne[] = "+IPD,5,200:GET /tup HTTP/1.1";
int linkID;
int reqSize;
const byte maxURLLength = 20;
char urlBuffer[maxURLLength];
boolean isHTTPRequest(char * s)
{
boolean correct = false;
char * ptr1, *ptr2, *ptr3;
if (ptr1 = strstr(s, "+IPD,")) {
if (ptr2 = strstr(s, ":GET /")) {
if (ptr3 = strstr(s, " HTTP")) {
linkID = atoi(ptr1 + 5);
byte b = *ptr3;
*ptr3 = '\0';
strncpy(urlBuffer, ptr2 + 5, maxURLLength));
urlBuffer[maxURLLength - 1] = '\0'; // strncpy might not put the trailing null
*ptr3 = b; // restore string as it was
correct = true;
}
}
}
return correct;
}
void setup() {
Serial.begin(115200);
if (isHTTPRequest(ligne)) {
Serial.println(linkID);
Serial.println(urlBuffer);
} else {
Serial.println(F("Erreur"));
}
}
void loop() {}
on obtient le même résultat, les valeurs sont bien lues même si je conviens que c'est moins intuitif - en gros voilà comment elle fonctionne:
On vérifie la présence de mots clés dans la ligne en cherchant avec strstr()
et on positionne des pointeurs (ptr1, ptr2, ptr3) sur ces mots clés. Si ces 3 pointeurs sont non nuls on considère que l'on a trouvé une phrase correcte et comme on sait où sont les éléments pertinents on utilise des fonctions standard atoi()
qui convertit du texte en entier et strncpy()
qui copie un certain nombre de caractères pour extraire les 2 éléments pertinents.
Cela dit côté mémoire le travail que l'on a fait à la main est payant. Parce que l'on sait ce que l'on cherche notre fonction peut être plus efficace en mémoire.
Le croquis utilise 2408 octets (0%) de l'espace de stockage de programmes. Le maximum est de 253952 octets.
Les variables globales utilisent 258 octets (3%) de mémoire dynamique, ce qui laisse 7934 octets pour les variables locales. Le maximum est de 8192 octets.
On gagne plus de 1500 octets de mémoire programme.
sscanf() est une fonction générique hyper puissante mais elle a un coût en empreinte mémoire. De plus en travaillant à la main je m'assure de ne pas dépasser la taille du buffer mémoire d'URL, chose que je ne contrôle pas bien avec sscanf() sans rajouter encore plus de code. Certes sur un MEGA on en a 253952 à disposition, on peut donc hésiter entre l'un est l'autre, mais ces 1500 octets vont potentiellement vous manquer à un moment surtout si vous voulez mettre en mémoire flash par exemple le code de votre site web.
Pour cette raison je laisse tomber la facilité de sscanf() et je pars sur la manipulation de pointeurs. vous voyez qu'avec un petit dessin ce n'est pas compliqué.
Mine de rien on progresse ! On a déjà une fonction utile qui nous dit si un buffer contient une requête bien formée et dans ce cas extrait les 2 éléments importants pour la génération de pages web en commande AT.
Attaquons nous au problème de lire une ligne dans un buffer de manière non bloquante. Voici une fonction gotLine() qui va lire sur un port série (que je définis modulaire pour plus tard), ignore les '\r' et répond vrai quand on a trouvé la fin de ligne '\n'. Dans ce cas le buffer est correctement constitué et stocké dans ESP_MessageLine
.
#define ESPSEPRIAL Serial
const byte maxMessageSize = 100;
char ESP_MessageLine[maxMessageSize + 1]; // +1 as we want to be able to store the trailing '\0'
// --------------------------------------
// read a line from ESPSEPRIAL, ignore '\r' and returns true if '\n' is found
// --------------------------------------
boolean gotLine()
{
static byte indexMessage = 0;
boolean incomingMessage = true;
while (ESPSEPRIAL.available() && incomingMessage) {
int c = ESPSEPRIAL.read();
if (c != -1) {
switch (c) {
case '\n':
ESP_MessageLine[indexMessage] = '\0'; // trailing NULL char for a correct c-string
indexMessage = 0; // get ready for next time
incomingMessage = false;
break;
case '\r': // don't care about this one
break;
default:
if (indexMessage <= maxMessageSize - 1) ESP_MessageLine[indexMessage++] = (char) c; // else ignore it..
break;
}
}
}
return !incomingMessage;
}
void setup() {
Serial.begin(115200);
}
void loop() {
if (gotLine()) {
Serial.print(ESP_MessageLine);
}
// here you can do something else!
}
La boucle tourne en allant voir et enregistrant tout ce qui arrive et quand on est prêt on affiche ce qu'on a lu et on recommence. Tout simple.
Seconde fonction utile dans la poche qui, combinée avec la première, va nous permettre d'écouter le port Série dans la loop() et une fois qu'on a reçu une ligne, tester simplement si c'est une requête GET que l'on doit gérer.
void loop() {
if (gotLine()) {
if (isHTTPRequest(ESP_MessageLine)) {
if (!strcmp(urlBuffer, "/")) { // on regarde si l'URL est pour le site racine
// si oui générer le web site
}
// fermer la connexion
}
}
// ici on peut faire autre chose du moment que ce n'est pas trop long
// ....
}
la structure prend forme!