Programmateur d'arrosage avec gestion web

Bonjour,

J'essaie péniblement de finir un programmateur d'arrosage, avec une supervision sur page web (pour une future intégration domotique).

Je réalise mon système en 3 mode : 1 mode arrêt (facile !), un mode automatique (avec déclenchement à heures fixes, et suivant une durée déterminée), et un mode manuel.
J'avais commencé par le mode automatique, en autonome sur un arduino mega.
Puis en passant sur le monde manuel, je me suis dit que ce serait pas mal de le faire à distance, d'où l'idée de revoir l'ensemble avec une gestion par un page html (qui ne sera pas généré par l'arduino). L'idée étant d'avoir une page principale avec la possibilité d'afficher 3 pages différentes pour chaque mode.

Je me suis donc lancé dans la partie mode manuel, en essayant de faire allumer mes réseaux d'arrosage (pour l'instant juste représentés par des leds), via une page web, avec un retour d'état.
Je parviens à faire allumer mes leds, mais je voulais également un retour d'état, et là ça bloque.
Je me suis inspiré du blog d'eskimon (Arduino et Ethernet : serveur • Le blog d'Eskimon) mais je ne parviens pas à le reproduire pour mon cas (j'ai d'ailleurs essayé le sien sans rien modifier, ça ne fonctionne pas non plus :frowning: )

Sur Firefox, j'ai un message dans la console :

XMLHttpRequest { onreadystatechange: onreadystatechange(), readyState: 4, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, responseURL: "", status: 0, statusText: "", responseType: "", response: "" }
ArduinoArrosage.js:49:5
Blocage d’une requête multiorigines (Cross-Origin Request) : la politique « Same Origin » ne permet pas de consulter la ressource distante située sur http://192.168.0.123/?r=1. Raison : l’en-tête CORS « Access-Control-Allow-Origin » ne correspond pas à « (null) ».

et sur Chrome j'ai :
XMLHttpRequest {onreadystatechange: null, readyState: 1, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, …}onabort: nullonerror: nullonload: nullonloadend: nullonloadstart: nullonprogress: nullonreadystatechange: ƒ ()ontimeout: nullreadyState: 4response: ""responseText: ""responseType: ""responseURL: "http://192.168.0.123/?r=5"responseXML: nullstatus: 200statusText: "OK"timeout:
Réponse reçue:
Uncaught SyntaxError: Unexpected end of JSON input
at JSON.parse ()
at afficher (ArduinoArrosage.js:68)
at XMLHttpRequest.requete.onreadystatechange (ArduinoArrosage.js:58)
afficher @ ArduinoArrosage.js:68
requete.onreadystatechange @ ArduinoArrosage.js:58
XMLHttpRequest.send (async)
executer @ ArduinoArrosage.js:50

Je vous mets mon code arduino, sans l'init et le setup :

void loop()
{
  // Regarde si un client est connecté et attend une réponse
  EthernetClient client = serveur.available();
  if (client) {
    // Quelqu'un est connecté !
    Serial.println("On envoi !");
    url = ""; // on remet à zéro notre chaîne tampon
    index = 0;
    while (client.connected()) { // Tant que le client est connecté
      if (client.available()) { // A-t-il des choses à dire ?
        // traitement des infos du client
        char carlu = client.read(); //on lit ce qu'il raconte
        if (carlu != '\n') { // On est en fin de chaîne ?
          // non ! alors on stocke le caractère

          url[index] = carlu;
          index++;
        } else {
          // on a fini de lire ce qui nous intéresse
          // on marque la fin de l'url (caractère de fin de chaîne)
          url[index] = '\0';
          Serial.println("message reçu de la page HTML :");
          Serial.println(carlu);
          boolean ok = interpreter(); // essaie d'interpréter la chaîne
          if (ok) {
            // tout s'est bien passé = on met à jour les réseaux
            Serial.println("On allume !");
            action();
          }
          // et dans tout les cas on répond au client
          repondre(client);
          // on quitte le while
          break;
        }
      }
    }
    // Donne le temps au client de prendre les données
    delay(10);
    // Ferme la connexion avec le client
    client.stop();
  }
}

void repondre(EthernetClient client) {
  // La fonction prend un client en argument

 Serial.println("\nRepondre"); // debug
  // On fait notre en-tête
  // Tout d'abord le code de réponse 200 = réussite
  client.println("HTTP/1.1 200 OK");
  // Puis le type mime du contenu renvoyé, du json
  client.println("Content-Type: application/json"); //application/json
  // Autorise le cross origin
  client.println("Access-Control-Allow-Origin: *");
  // Et c'est tout !
  // On envoi une ligne vide pour signaler la fin du header
  client.println();


  // Puis on commence notre JSON par une accolade ouvrante
  client.println("{");
  // On affiche les réseaux dans un tableau
  client.println("\"reseau\": {");
  // Le réseau 1
  client.print("\t\t\"1\": ");
  client.print(etats[0]);
  client.println(",");
  // Le réseau 2
  client.print("\t\t\"2\": ");
  client.print(etats[1]);
  client.println(",");
  // Le réseau 3
  client.print("\t\t\"3\": ");
  client.print(etats[2]);
  client.println(",");
  // Le réseau 4
  client.print("\t\t\"4\": ");
  client.print(etats[3]);
  client.println(",");
  // Le réseau 5
  client.print("\t\t\"5\": ");
  client.print(etats[4]);
  client.println(",");
  // Le réseau 6
  client.print("\t\t\"6\": ");
  client.println(etats[5]);
  client.println("\t\t}");
  // Et enfin on termine notre JSON par une accolade fermante
  client.println("}");
  Serial.println("réponse envoyée !");
}

boolean interpreter() {
  // On commence par mettre à zéro tous les états
  for (int i = 0; i < 6; i++) {
    etats[i] = LOW;
  }

  // Puis maintenant on va chercher les caractères/marqueurs un par un.
  index = 4; // Index pour se promener dans la chaîne (commence à 4 pour enlever "GET "
  while (url[index] != 'r' && url[index+1] != '=') { // On commence par chercher le "r="
    index++; // Passe au caractère suivant
    if (index == 100) {
      // On est rendu trop loin !
      Serial.println("Oups, probleme dans la recherche de 'r='");
      return false;
    }
  }
  // Puis on lit jusqu’à trouver le ' ' de fin
  while (url[index] != ' ') { // On cherche le ' '
    if (url[index] >= '1' && url[index] <= '6') {
      // On a trouvé un chiffre identifiant un réseau
      char reseau = url[index] - '0'; // On ramène ça au format décimal
      etats[reseau - 1] = HIGH; // Puis on met le réseau dans un futur état haut
    }
    index++; // Passe au caractère suivant
    if (index == 100) {
      // On est rendu trop loin !
      Serial.println("Oups, probleme dans la lecture des broches");
      return false;
    }
    // NOTE : Les virgules séparatrices sont ignorées
  }

  // Rendu ici, on a trouvé toutes les informations utiles !
  Serial.println("interprétation OK");
  return true;
}

Sauriez-vous me dire où est l'erreur ?
Merci par avance, en espérant que ce soir lisible...

et mon code .js

var reseau = []; // Tableau réseaux
var etatReseau = []; // Tableau d'etat des broches
var adresse = "http://192.168.0.123:80/"; // L'url+port du shield

document.addEventListener('DOMContentLoaded', setup, false);

function setup() {
    // fonction qui va lier les variables à leur conteneur HTML
    reseau[1] = document.getElementById("reseau1");
    reseau[2] = document.getElementById("reseau2");
    reseau[3] = document.getElementById("reseau3");
    reseau[4] = document.getElementById("reseau4");
    reseau[5] = document.getElementById("reseau5");
    reseau[6] = document.getElementById("reseau6");
    etatReseau[1] = document.getElementById("etatReseau1");
    etatReseau[2] = document.getElementById("etatReseau2");
    etatReseau[3] = document.getElementById("etatReseau3");    
    etatReseau[4] = document.getElementById("etatReseau4");
    etatReseau[5] = document.getElementById("etatReseau5");
    etatReseau[6] = document.getElementById("etatReseau6");
    
    // La fonction concernant le bouton
    var bouton = document.getElementById("envoyer");
    bouton.addEventListener('click', executer, false);
}

function executer() {
    // Fonction qui va créer l'url avec les paramètres puis
    // envoyer la requête
    var requete = new XMLHttpRequest(); // créer un objet de requête
    var url = adresse;
    url += "?r=";
    for(i=1; i <= 6; i++) { // Pour les réseaux 1 à 6 de notre tableau
        if(reseau[i].checked) // si la case est cochée
            url += i + ",";
    }
    // enlève la dernière virgule si elle existe
    if(url[url.length-1] === ',')
        url = url.substring(0, url.length-1);
    
    console.log(url) // Pour debugguer l'url formée    
    requete.open("GET", url, true);
    console.log(requete) // Pour debugguer la requete  
    requete.send(null); // On envoie !
    requete.onreadystatechange = function() { // on attend le retour
        if (requete.readyState == 4) { 
        console.log(requete.readyState);        
        console.log(requete.status);
        console.log("Réponse reçue: %s", requete.responseText);// Revenu !
            if (requete.status == 200) {// Retour s'est bien passé !
                // fonction d'affichage (ci-dessous)
                afficher(requete.responseText);
            } else { // Retour s'est mal passé :(
                alert(requete.status, requete.statusText);
            }
        }
    };
}

function afficher(json) {
    // Affiche l'état des réseaux revenu en json
    var donnees = JSON.parse(json);
    console.log("données reçue: %s",donnees);
    
    for(i=1; i <= 6; i++) { // Pour les reseaux 1 à 6 de notre tableau
        etatReseau[i].checked = donnees["reseau"][i];
    }
}

Je vous mets mon code arduino, sans l'init et le setup :
...

...

url = ""; // on remet à zéro notre chaîne tampon
...
  url[index] = carlu;
...

Pas une bonne idée de nous donner que la moitié des infos... par exemple comment est définie url ?

Salut

J'ai déjà en ce genre de problème sur un serveur que j'ai développé dans mon entreprise.
Sur chaque poste client est installé un petit serveur HTTP qui sert de passerelle pour communiquer avec un matériel relié au PC par une ligne série.
Le javascript communique avec ce petit serveur comme tu le fais avec ton ARDUINO, sauf que cela se passe chez moi sur 127.0.0.1.
EN HTTP tout se passait bien.
En HTTPS par contre la console affichait ce message "Blocage d'une requête multiorigines".
J'ai été donc obligé de modifier mon petit serveur local pour qu'il accepte les requêtes HTTPS.
Ce serveur est utilisé en production chez nous - en interne - et aussi à l'étranger à travers un proxy HTTPS pour des raisons de sécurité.
Donc, en fonction de l'endroit où se situe le poste client on a :

  • en interne : serveur principal en accès direct HTTP et serveur local en HTTP
  • en externe : serveur principal en accès HTTPS et serveur local en HTTPS

La demande "Access-Control-Allow-Origin: *" ne résoud en rien le problème.

Donc pour résumer : l'URL que tu entres dans ton navigateur FIREFOX ou CHROME commence t-elle par HTTPS ?

@+

On entend par origine la combinaison d'un protocole, un hôte et d'un numéro de port. Le navigateur isole les différents documents en fonction de leur origine et, en théorie, il n'est pas possible d'accéder à un contenu d'une origine A depuis l'origine B

Plus d’infos sur le multi-origines (nécessaire pour la sécurité) en lisant ceci par exemple et donc aller ensuite explorer CORS (Cross-Origin Resource Sharing)

Bonjour,

Merci pour vos réponses.

J-M-L, la création de mon url est dans mon fichier javascript.

function executer() {
    // Fonction qui va créer l'url avec les paramètres puis
    // envoyer la requête
    var requete = new XMLHttpRequest(); // créer un objet de requête
    var url = adresse;
    url += "?r=";
    for(i=1; i <= 6; i++) { // Pour les réseaux 1 à 6 de notre tableau
        if(reseau[i].checked) // si la case est cochée
            url += i + ",";
    }
    // enlève la dernière virgule si elle existe
    if(url[url.length-1] === ',')
        url = url.substring(0, url.length-1);
    
    console.log(url) // Pour debugguer l'url formée    
    requete.open("GET", url, true);

Elle est de la forme http://192.168.0.123:80/?r=1
192.168.0.123:80 est l'adresse de mon arduino sur mon réseau, avec le port 80.

hbachetti, mon adresse est bien de la forme http (enfin celle pour envoyer la requete à mon arduino).
celle du retour est de la forme file:///C:/dossier ou est présent pour le moment ma page .html, mon .css et mon .js

J-M-L, merci pour tes liens, je vais les lire avec attention.

Salut

Le mélange d'origines HTTP HTTPS n'est pas possible, mais cela serait étonnant que la même règle ne s'applique pas pour HTTP et FILE.
Il faudrait vérifier en faisant la présentation de ta page par un serveur APACHE, mais je suis prêt à parier que c'est le cas.

@+

Salut,

Je ne connais pas Apache.
Je viens de l'installer sur mon pc, et de le configurer (enfin je crois)
Je peux ouvrir ma page (qui s'appelle ArduinoArrosage.html) sur http://127.0.0.1/ArduinoArrosage.html

Malheureusement, j'ai toujours la même erreur quand je veux allumer une led. Elle s'allume mais je n'ai pas l'info en retour de l'état.
Ce qui me semble bizarre, c'est que j'ai l'impression que sous firefox il n'y a pas de retour du tout (statut de la réponse =0), alors que sous chrome, j'ai bien 200

Il ya peut être une solution avec l'utilisation de JSONP, mais j'avoue que j'ai du mal à le mettre en oeuvre...
JSONP

Je me suis souvenu que le serveur doit être configuré également pour autoriser le "Access-Control-Allow-Origin".

ICI

@+

J'ai ajouté à la fin du fichier de config du serveur apache

#Accept cross-domain requests

Header always set Access-Control-Allow-Origin "*"

Je ne sais plus trop quoi faire, et surtout où...

<IfModule mod_headers.c>

J'ai des doutes.

Plutôt comme ça ?

@+

Bonjour,
Une suggestion serait d'utiliser un Arduino Yun qui offre des possibilités de gestion assez simple via des pages web (html ou php).
J'ai réalisé un projet "thermostat commandé via Internet" (Thermostat via Internet avec Arduino Yun, DHT22 et relais - Réalisations et Projets Finis - Arduino Forum).
L'Arduino Yun existe maintenant en Rev2 à un prix qui devient assez intéressant.
La programmation via les commandes REST est assez simple à mettre en oeuvre.

Hello,

J'ai essayé de modifier le fichier config de apache, mais il ne démarre plus...
Pour le Yun, j'ai déjà un mega avec un shield, j'aimerais tout de même pouvoir l'utiliser.

Est-ce que c'est le serveur qui doit autoriser le cross domain, ou juste le site ?
Sur une page html avec un fichier javascript, comment autorise-t-on le cross domain?

J'ai essayé de modifier le fichier config de apache, mais il ne démarre plus...

Il y a certainement une erreur de syntaxe.
Lance-le en ligne de commande, il affichera peut-être un message.

Je désespère
Je passe des soirées à essayer de résoudre mon problème, rien n’y fait...
Je trouve beaucoup d’exemples sur le net avec une page web hébergée sur l’arduino, mais très peu avec une page hébergée ailleurs...hormis celui d’eskimon

Votre PC est dédié au serveur web? Vous allez le laisser allumé en permanence sans mode sommeil pour attendre les requêtes ? ça semble être une débauche d’energie et matérielle pour un usage limité...

Perso j’ai pris une offre chez OVH pour les bidouilles - j’ai pris l’équivallent De leur offre Performance 1 (mais à l’époque c’était pas appelé comme cela) car j’ai des usages un peu avancés sur certains trucs mais Vous pouvez avoir l’offre perso avec 1 nom de domain, 100 Go d'espace disque, 10 adresses e-mails et Trafic illimité pour moins de 3,60€ par mois...

vous avez aussi l’offre d'hébergement Web Kimsufi à 1,80€ par mois qui est largement suffisante pour commencer avec nom de domaine, possibilité d’envoi de mails, le support de PHP et Python et la possibilité d’1 base de données MySQL incluse de 100 Mo avec 30 Connexions simultanées;..

À ce prix là je ne vois pas pourquoi s’embetter à dédier un pc à la maison avec le courant, la bande passante, l’ouverture du réseau / création de DMZ, les backups, le firewall etc...

A terme, l'idée est d'avoir un serveur domotique dédié (J'ai un raspberry qui pourra faire ça), je ne sais pas bien encore sous quelle forme. Soit en box jeedom si j'en peux ajouter ma page, soit un serveur spécifique.

Pour le moment je construis ma page , et j'essaie de construire mon code de communication dans les 2 sens.
Je suis parti du principe que mon arduino devait être en mode serveur pour communiquer et recevoir les requêtes, mais est ce la bonne option ?

Hello,

Après de nombreux essais en tout genre (serveur WAMP, intégrer du php pour gérer les requête et les retours Arduino ...) j’ai finalement réglé mon problème en utilisant la librairie AduinoJson.
Je ne sais pas pourquoi ça fonctionne, mais ça fonctionne, c’est le principal !
J’ai donc pour l’instant une interface (moche) pour piloter en manuel mes futures électrovannes.

Il me reste à créer/améliorer ma page web, avec un menu pour les différents modes d’arrosage, et paramètres, avec les codes qui vont bien dans mon sketch pour traiter les requêtes !

Pleins de soirées entre perspectives !