toutes les 5 minutes l'Aduino doit effectuer une requête HTTP vers mon application web pour récupérer des variables (que je renvoie au format JSON)
certaines parties du programme de l'Arduino préviennent l'application web, via une requête HTTP, que le programme est dans tel ou tel état (ex. : Début Cycle manuel / Fin Cycle / Etc.
J'ai effectué le tutoriel donné par Arduino pour tester le fonctionnement le Web Client de l'Arduino Shield 2. Tout fonctionne à merveille. J'ai adapté avec une page de test sur mon site web qui renvoie des données au format JSON et ça fonctionne bien. Cf. : https://www.arduino.cc/en/Tutorial/GSMExamplesWebClient
C'est ensuite que ça se corse, dès que j'essaie de l'adapter à mon code plus rien ne fonctionne.
Du coup je suis parti sur quelque chose de relativement simple pour commencer mais c'est déjà un fiasco général.
// Include des librairies
#include <GSM.h>
// Code PIN de la carte SIM
#define PINNUMBER ""
// Informations de connexion au réseau GPRS
#define GPRS_APN "orange"
#define GPRS_LOGIN "orange"
#define GPRS_PASSWORD "orange"
// Initialisation des instances
GSMClient client;
GPRS gprs;
GSM gsmAccess;
// URL, path & port
#define APIToken "temp_token"
char server[] = "monsiteweb.com";
char path[] = "/arduinoIn/cyclique";
int port = 80;
// Paramètres pour l'appel cycle de la requête HTTP
const unsigned int delayRequest = 50000; // Je me limite à 50s le temps de la programmation
unsigned long lastRequest;
void setup()
{
// initialize serial communications and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
Serial.println("Starting Arduino web client.");
// connection state
boolean notConnected = true;
// After starting the modem with GSM.begin()
while (notConnected) {
if ((gsmAccess.begin(PINNUMBER) == GSM_READY) &
(gprs.attachGPRS(GPRS_APN, GPRS_LOGIN, GPRS_PASSWORD) == GPRS_READY)) {
notConnected = false;
Serial.println("GSM - Connected");
} else {
Serial.println("GSM - Not connected");
delay(1000);
}
}
}
void loop() {
// Appel cyclique de la requête HTTP
if((millis() - lastRequest) >= delayRequest)
{
Serial.println("Appel de callRequest dans la boucle 'LOOP'");
// Appel de la fonction Request
callRequest();
// Update de lastRequest
lastRequest = millis();
}
}
void callRequest()
{
Serial.println("Début de Call Request'");
// if you get a connection, report back via serial:
if (client.connect(server, port)) {
Serial.println("connected");
// Make a HTTP request:
client.print("GET ");
client.print(path);
client.println(" HTTP/1.1");
client.print("Host: ");
client.println(server);
client.println("Connection: close");
client.println();
} else {
// if you didn't get a connection to the server:
Serial.println("connection failed");
}
// if there are incoming bytes available
// from the server, read them and print them:
if (client.available()) {
char c = client.read();
Serial.print(c);
}
// if the server's disconnected, stop the client:
if (!client.available() && !client.connected()) {
Serial.println();
Serial.println("disconnecting.");
client.stop();
// do nothing forevermore:
for (;;)
;
}
}
Dès que je sors du tutoriel avec la requête HTTP dans "SETUP" et la lecture du "read" dans loop rien ne fonctionne.
Pour information dans le moniteur ça m'affiche le code suivant :
Starting Arduino web client.
GSM - Connected
Appel de callRequest dans la boucle 'LOOP'
Début de Call Request'
connected
Appel de callRequest dans la boucle 'LOOP'
Début de Call Request'
connection failed
Appel de callRequest dans la boucle 'LOOP'
Début de Call Request'
connection failed
Appel de callRequest dans la boucle 'LOOP'
Début de Call Request'
connection failed
Je ne suis pas contre un peu d'aide pour me remettre sur le bon chemin
si connexion ok:
envoyer une requête http au serveur
sinon:
afficher un message
quel que soit le résultat du point 2, si 1 caractère au moins est disponible, l'afficher
toujours quel que soit le résultat du point 2, si aucun caractère n'est disponible, et que l'on n'est plus connecté:
fermer la connexion
attendre indéfiniment
Le déroulé de ton test:
On entre dans callRequest
Ça se connecte (passage au point 1)
La connexion est bonne, envoi de la requête (passage au point 2)
Le serveur n'a pas encore répondu quoi que ce soit, le test au point 3 échoue, rien n'est affiché
Un caracère est disponible (peu probable) ou on est connecté, le test au point 4 échoue, on n'affiche pas 'disconnected'
On sort de la fonction callRequest
50 sec plus tard:
On retourne dans callRequest
La connexion échoue cette fois (peut-être parce que la première est toujours active, ou bien que la bibliothèque GSM ne supporte qu'une connexion active à la fois, je ne connais pas bien cette bibiliothèque)
À partir d'ici, ça devient un peu flou pour moi, car à ce point la connexion initiale est sans doute encore valable ('client' est une variable globale), du coupe on devrait peut-être avoir un début de réponse du serveur et afficher un caractère au test 3.
Ce qui ne va pas:
Déjà les points 3 et 4 ne devraient pas être effectués si la connexion au point 2 a échoué
Point 3 et 4 à réorganiser:
La communication avec un serveur sur un réseau, c'est comme un dialogue avec un humain: quand on pose un question et qu'on veut une réponse, on attend que l'interlocuteur commence à parler et on attend la fin de sa réponse, on ne se barre pas avant qu'il ait ouvert la bouche (il faut quand même lui laisser le temps de comprendre la demande), ni en plein milieu de la réponse !
Proposition d'amélioration:
se connecter au serveur
si échec, sortir de la fonction
envoyer la requête http
tant qu'on est connecté avec le serveur:
si un caractère est reçu, l'afficher
fermer la connexion
Ce qui pourrait se traduire par:
void callRequest()
{
Serial.println("Début de Call Request'");
// 1. se connecter au serveur:
if (!client.connect (server, port))
{
// 2. si échec, sortir de la fonction:
Serial.println ("connection failed");
return;
}
Serial.println ("connected");
// 3. envoyer la requête http:
client.print("GET ");
client.print(path);
client.println(" HTTP/1.1");
client.print("Host: ");
client.println(server);
client.println("Connection: close");
client.println();
// 4. tant qu'on est connecté avec le serveur:
while (client.connected ())
{
if (client.available())
{
// si un caractère est reçu, l'afficher:
char c = client.read();
Serial.print(c);
}
}
// 5. fermer la connexion
Serial.println("disconnecting.");
client.stop();
}
Bon, j'ai pas compilé ni testé, mais ça devrait être quelque chose comme ça.
Améliorations ultérieures:
Au point 4, en plus d'afficher la réponse (ou à la place), mémoriser l'ensemble de la réponse
Au point 5, après avoir fermé la connexion, agir en fonction de la réponse
@aladec : c'était ma grande crainte lors du choix de l'hébergeur, par chance les tests effectués en chargeant une page sont concluants.
@cbrandt : merci pour tes instructions et l'explication pas à pas. Je suis reparti sur ta méthode pour callRequest(). Et ça change tout !!! J'arrive à appeler ma page toutes les 50s. (lors de l'appel de la page je le trace dans ma BDD).
Je tombe sur une nouvelle problématique : la variable "c" est tronquée. Je m'explique :
En utilisant la méthode donnée par le tutoriel Arduino dans le moniteur série j'ai la réponse suivante d'affichée :
En partant sur ta proposition le message de réponse est tronqué et le message "disconnecting" vient s'ajouter directement à la fin de ma requête :
Appel de callRequest dans la boucle 'LOOP'
Début de Call Request'
connected
HTTP/1.1 200 OK
Server: o2switch PowerBoost
Date: Sat, 08 Sep 2018 08:20:09 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: close
Vary: Accept-Encoding
Cache-Control: private, must-revalidate
pragma: no-cache
expires: -1
Set-Cookie: XSRF-TOKEN=eyJpdiI6IlpoYlZcL0V0ZjJKQ3lxd21ZaUlzemZnPT0iLCJ2YWx1ZSI6InkzYnpQK3A4XC96clROeERQSjVaV1B2aW1BUFFFajFHWCtGSDBybnFsaWU4blhZSGozZjJOWjhrUzZ3QkVySHpxIiwibWFjIjoiZjcyY2JlZmQ0ZTgyMTdhYmRiYzZhY2YwOGU1YjMyYTM3NzVkYmE2MmMyODU2ZmFhY2U4YTcxODY3ZWFkODM2MSJ9; expires=Sat, 08-Sep-2018 10:20:09 GMT; Max-Age=7200; path=/
Set-Cookie: mywebsite_session=eyJpdiI6IjEyKzVFQnFoVVE4VVp1Yk9FdjQreVE9PSIsInZhbHVlIjoiOG04ZWpLQzhIN2lvVDQyNnNkN056XC9XUlpSZldHNVlWTVlsRFltQVNjXC9YZlBYNTV4ajcrdXlLVk5rZk9pMUdRIiwibWFjIjoiN2IyNTFlYmUxODRjZDU5NDA4ODlmMTY5NzdlOWY2MWE2NzU5YzFlNzk2MTk4ZTI3MTk4NjI5MzQ1NDhhYzMwZSJ9; expirdisconnecting.
Je me suis d'abord dit que Serial avait peut-être un peu de mal à digérer la longueur du message retourné par la requête. J'ai mis un delay(5000) avant de faire le client.stop(); mais j'ai toujours le même problème.
Je vais voir pour que dans la variable "c" je puisse directement inscrire le contenu JSON retourné par la requête.
La partie data est séparée des entêtes par un double passage à la ligne:
Il faut tester la réception successive des 4 caractères \r\n\r\n
Le champ content-length de l'entête te donnera la longueur des data - tu peux t'en servir pour allouer la variable dans laquelle tu stockeras les données json.
Edit: je viens de voir que le Transfer-Encoding renvoyé par le serveur est " chunked"
Dans ce cas le transfert de fait par blocs, et la taille de chaque bloc est indiquée: dans ton exemple c'est 14 pour le premier bloc. Donc tu lis 14 octets à mémoriser. Et ainsi de suite jusqu'à tomber sur un bloc de longueur 0: à ce moment toutes les données ont été transmises.
Oui c'est un solution plus rapide mais moins universelle. En tout cas il ne faudra pas que le format change... si tu as la main sur le serveur, ça peut aller, sinon le jour où le gestionnaire du serveur décidé de changer un truc... crack !
Je n'ai pas la main sur le serveur, je vais sécuriser en cherchant le JSON renvoyé.
cbrandt:
La partie data est séparée des entêtes par un double passage à la ligne:
Il faut tester la réception successive des 4 caractères \r\n\r\n
Le champ content-length de l'entête te donnera la longueur des data - tu peux t'en servir pour allouer la variable dans laquelle tu stockeras les données json.
Edit: je viens de voir que le Transfer-Encoding renvoyé par le serveur est " chunked"
Dans ce cas le transfert de fait par blocs, et la taille de chaque bloc est indiquée: dans ton exemple c'est 14 pour le premier bloc. Donc tu lis 14 octets à mémoriser. Et ainsi de suite jusqu'à tomber sur un bloc de longueur 0: à ce moment toutes les données ont été transmises.
Dans tous les cas je suis obligé de rechercher la succession de "\r\n\r\n" pour savoir que j'en ai fini avec l'header que j'arrive dans la partie data retournée par le serveur ?
Pour une remise à niveau sur le protocole HTTP, le document est nickel. :o
Par contre, sorry cbrandt mais je reste en galère
Je pense que je n'ai pas du tout la bonne piste de travail. A première vue il me semble compliqué d'effectuer le parcours de "c" directement. A la limite si j'avais un tableau de "c" -> "c[]" ça me semblerait plus jouable.
Afin de continuer dans la bonne voie (enfin tout au moins me débloquer), je me pose les questions suivantes :
dois-je continuer sur une variable de type "char" ou la convertir en "string" par exemple ?
pour l'endroit où je fais ce traitement dans le point 4, ou après avoir fermé la connexion ?
PS : j'ai tenté de voir si je pouvais tout simplement utiliser la librairie "Http Client" mais je n'ai pas de solution pour faire le pont avec la librairie GSM du Arduino GSM Shield 2.
A croire qu'on n'est pas nombreux à vouloir récupérer le résultat de la requête effectuée. Sur les forums, principalement je n'ai que pour l'envoie de données.
Bon, j'ai trouvé une solution qui fonctionne, elle est complétement barbare mais après des heures d'essais je n'ai rien de mieux... Si vous avez des conseils pour améliorer je suis preneur :
// 4. tant qu'on est connecté avec le serveur:
boolean startData = 0;
boolean endData = 0;
boolean validFirst = 0;
String httpData = "";
while(client.connected ())
{
while (client.available())
{
// si un caractère est reçu, l'afficher:
char c = client.read();
String s = String(c);
// Recherche des deux premières caractères annoncant le début du JSON
if (s == "\"" && !validFirst && !startData)
{
validFirst = 1;
}
else if (s == "{" && validFirst && !startData)
{
startData = 1;
}
else
{
validFirst = 0;
}
// Ajout des caractéres du JSON tant qu'on atteind pas sa fin par }
if ((startData || validFirst) && s != "}" && !endData)
{
httpData += s;
}
else if ((startData || validFirst) && s == "}" && !endData)
{
httpData += "}\"";
endData = 1;
}
else
{
}
}
}
Serial.print(httpData);
// 5. fermer la connexion
Serial.println("disconnecting.");
client.stop();
Le Serial.print(httpData); me ramène bien que la partie JSON :
"{"value1":"la valeur 1","value2":"la valeur 2","value3":"la valeur 3"}"
Ta solution n'est pas (trop) barbare… il faut juste espérer que les données arriveront bien en 1 seul 'chunk' (ce qui à priori devrait toujours être ton cas: pas beaucoup de données, et la connexion n'est pas réutilisée).
Il faudrait juste remplacer l'utilisation de la classe 'String' par des chaînes C standard. Il y a peut-être aussi moyen de simplifier un peu (en effet les " de début et fin ne font pas partie du json)
// 4. tant qu'on est connecté avec le serveur:
boolean keepData = false;
char httpData[80]; // on autorise max 80 caractères pour le json
int ptr = 0; // index d'écriture dans httpData
while(client.connected ())
{
while (client.available())
{
char c = client.read();
// Recherche du premier caractère annoncant le début du JSON
if (c == '{')
keepData = true;
else if (c == '}')
keepData = false;
// Ajout des caractères du JSON si keepData est vrai et qu'il reste de la place dans httpData
if (keepData && ptr < 78) // 78 car il faut garder de la place pour le '}' et le '\0' de fin (marqueur de fin de chaîne)
httpData[ptr++] = c;
}
}
httpData[ptr++] = '}';
httpData[ptr] = 0;
Serial.println (httpData);
// 5. fermer la connexion
Serial.println("disconnecting.");
client.stop();
Attention cependant, cette solution n'est pas universelle pour récupérer un json quelconque… elle ne convient que pour une réponse formatée comme celle que tu reçois: un dictionnaire json qui ne contient pas d'autre dictionnaire json.
EDIT:
d'ailleurs il y a un truc bizarre avec ce serveur… il y a les " qui entourent le json mais aussi chaque " qui fait partie du json est précédée d'un
En fait il semblerait qu'il y ait comme un encapsulage d'une structure json dans une chaîne de caractères json… Peut-être regarder du côté de l'application PHP qui génère la réponse ?
En json la réponse devrait être ceci:
En effet, c'est largement plus lisible J'avais mis la recherche de la succession de " et de { au cas ou qu'un { apparaisse dans le header. En jetant un coup d’œil à mon application web je n'ai pas de risque que ça apparaisse.
Alors je viens de faire deux essais sur mon application en lançant la requête directement dans mon navigateur. (Pour information j'utilise le framework Laravel).
Et je confirme pour le JSON j'avais bien fait une erreur dans la programmation de mon application web ! (je disais à mon application de convertir une réponse en JSON alors qu'elle était déjà en JSON).
Désormais j'ai de retourné :
Starting Arduino web client.
GSM - Connected
Appel de callRequest dans la boucle 'LOOP'
Début de Call Request'
connected
{"value1":"la valeur 1","value2":"la valeur 2","value3":"la valeur 3"}
disconnecting.
Merci de ton aide pour ce projet, j'avais largement de quoi perdre mes cheveux avant de trouver la solution !
Je formate la solution finale pour passer le topic en résolu !
Bonjour,
Je me demandais pour un projet de ce genre s'il n'était pas plus intéressant d'utiliser un Arduino Yun v2 ?
C'est plus coûteux mais la dépense en énergie pour la recherche et la programmation serait moindre.
De plus le Yun propose un serveur web local. Le sketch serait plus simple à réaliser via l'utilisation du "pont" qui permet d'échanger les données avec le Linux embarqué. Pas de Shield Ethernet à ajouter et on dispose d'un slot pour carte sd et du wifi.
Dans tous les cas j'avais besoin d’accéder à internet via le réseau GSM. Donc je me serai retrouvé à ajouter un module GSM qui est la partie la plus onéreuse du projet.