Librairie domotique: REST-like interface

[adsGrandRectangleTexte]

Pour communiquer avec les Arduino, j’utilise (en plus d’xPL) des requêtes HTTP « REST-like » de ce type :

http://ip_arduino/lumiere1/on
http://ip_arduino/add/lumiere2/1/13
http://ip_arduino/del/lumiere2

L’idée originale vient du projet RESTduino. La partie du code qui parse l’url à été en partie intégrée dans la librairie.

Si vous vous souvenez, la librairie permet d’ajouter des « pages » à servir par le serveur http via les méthodes AddPage() et AddDefaultPage()

Si nous regardons une fonction définissant le comportement d’une « page », on voit qu’elle reçoit en paramètre le premier argument de l’url demandée. Dans les exemples ci-dessus, cela correspond à lumiere1 et lumiere2. Add et del sont eux des noms de pages à servir.

On peut ensuite demander les arguments suivants via la méthode GetNextArg().
Ici on affiche tous les arguments de l’url demandée :

void showUrlArg(char* arg1)
{
    int i = 0;
    while(arg1 != NULL)
    {
        server.Print("Argument ");
        server.Print(++i);
        server.Print(": ");
        server.Println(arg1);
        arg1 = server.GetNextArg();
    }
 }

Implémentation

Les fonctions ParseUrl et GetNextArg de la classe HttpServeur qui servent à parser l’url demandée et à récupérer ses arguments :

char* HttpServer::ParseUrl(char *str)
{
  int8_t r=-1;
  int8_t i = 0;
  int index = 4;
  int index1 = 0;
  int plen = 0;
  char ch = str[index];
  char clientline[CMDBUF];
  while( ch != ' ' && index < CMDBUF)
  {
    clientline[index1++] = ch;
    ch = str[++index];
  }
  clientline[index1] = '\0';

  // convert clientline into a proper
  // string for further processing
  String urlString = String(clientline);

  // extract the operation
  String op = urlString.substring(0,urlString.indexOf(' '));

  // we're only interested in the first part...
  urlString = urlString.substring(urlString.indexOf('/'), urlString.indexOf(' ', urlString.indexOf('/')));

  // put what's left of the URL back in client line
  urlString.toCharArray(clientline, CMDBUF);

  return clientline;
}

char* HttpServer::GetNextArg(char *str)
{
  static char clientline[CMDBUF];

  if(str != NULL)
  {
	  sprintf(clientline,"%s",ParseUrl(str));
	  return strtok(clientline,"/");
  }
  else
  {
	  return strtok(NULL,"/");
  }
}

Un exemple concret

Ici c’est la page par défaut que j’utilise. Celle qui est appelée si aucune autre page ne correspond.
– Si aucun argument (ex : http://192.168.0.133/), j’affiche la configuration de tous les modules en json (via l’objet manager … j’en reparlerai dans un autre article, voir les sources pour les curieux)
– Si au moins un argument existe (ex : http://192.168.0.133/113/1), je recherche le module correspondant. Ensuite, en fonction de son type, j’effectue une action. Dans le cas d’une lumière je demande le deuxième argument et je modifie la valeur du module.

void Default(char* arg1)
{
  // Si aucun argument, on affiche l'état des modules en Json
  if(arg1 == NULL)
  {
    server.Print(manager.ToJson());
  }
  else
  {
    // Recherche du module demandé, arg1 étant l'id du module demandé
    Module* module = manager.FindModule(atoi(arg1));

    if(module != NULL)
    {
      // En fonction du type de module on lance une action
      switch(module->GetType())
      {
        case LIGHT:
          {
            Light* l = (Light*)module;
            // Retourne l'argument suivant: la valeur à paramétrer
            char* value = server.GetNextArg();
            if(value != NULL)
            {
              l->Set(atoi(value));
            }
            break;
          }
        case SHUTTER:
          {
            // traitement ...
          }

        // Gestion des autres cas ...
      }

      // Puis on affiche l'état du module en Json
      server.Print(module->ToJson());
    }
    else
    {
      // Aucun module trouvé, on suppose que l'utilisateur veut forcer l'état d'une io.
      // On tente de basculer la pin correspondante
      char* value = server.GetNextArg();
      digitalWrite(atoi(arg1), atoi(value));
      server.Print("Force pin ");
      server.Print(arg1);
      server.Print(" to value ");
      server.Println(value);
    }
  }
}

Voilà pour aujourd’hui :), bonne journée.

Vous aimerez aussi...

10 réponses

  1. Michael dit :

    Bravo pour ces articles super interessants, je debute dans la domotique (mais pas en informatique ou en electronique) et je souhaite « domotiser » les equipements que je possede. arduino, Raspberrypi, volet radio nice, 1wire et sonde oregon.
    j’attends avec impatience les prochains articles sur l’oregon 😉
    je vais tester ta librairie dans les semaines qui suivent.
    Grand MERCI pour ce partage.

    • Olivier Lebrun dit :

      Merci ça fait paisir 🙂
      Ils sont presque prets mais j’ai encore deux trois détails à régler (une petite amélioration « cosmétique » sur le décodeur et des tests de portée). Etant en plein déménagement tout est en carton :/

      N’hésite pas à me remonter les problèmes que tu rencontre sur la librairie. à bientot

      • Michael dit :

        C’est parti, je viens de mettre un peu a jour mes logiciels,
        Arduino 1.0.1, le dernier ethercard et la librairie connectingstuff
        mais des que je compile RESTLikeInterface j’obtiens ces messages d’erreur, il doit manquer une librairie !!!

        RESTLikeInterface:3: error: ‘HttpServer’ does not name a type
        RESTLikeInterface.cpp: In function ‘void setup()’:
        RESTLikeInterface:18: error: ‘EthernetConf’ has not been declared
        RESTLikeInterface:21: error: ‘server’ was not declared in this scope
        RESTLikeInterface.cpp: In function ‘void loop()’:
        RESTLikeInterface:38: error: ‘server’ was not declared in this scope
        RESTLikeInterface.cpp: In function ‘void Default(char*)’:
        RESTLikeInterface:49: error: ‘server’ was not declared in this scope
        RESTLikeInterface:67: error: ‘server’ was not declared in this scope
        RESTLikeInterface:80: error: ‘server’ was not declared in this scope
        RESTLikeInterface:86: error: ‘server’ was not declared in this scope
        RESTLikeInterface.cpp: In function ‘void AddModule(char*)’:
        RESTLikeInterface:100: error: ‘server’ was not declared in this scope
        RESTLikeInterface:113: error: expected `)’ before ‘;’ token
        RESTLikeInterface.cpp: In function ‘void DelModule(char*)’:
        RESTLikeInterface:122: error: ‘server’ was not declared in this scope

        Merci

        • Olivier Lebrun dit :

          A priori je dirais qu’il ne trouve pas la lib connectingstuff ?
          J’essai de faire un test rapidement et je reviens vers toi.

        • Olivier Lebrun dit :

          J’ai trouvé, je te corrige ça.

          J’ai dû uploader la dernière version sans la tester. Je corrige deux trois erreurs
          de syntaxe et c’est bon.

          Pour le problème que tu as, en fait dans Kernel.h tu peux activer/désactiver des morceaux de code (la partie ethernet, xPL LCD …) et par défaut j’ai tout désactiver. Il faut que tu remplace #undef ETH par un #define ETH.
          Je n’ai pas trouver comment le configurer directement dans le sketch, il y a un problème de visibilité des constantes entre l’environnement Arduino et les librairies. Donc pour le moment je suis obligé de le laisser dans la librairie, ce qui n’est pas fameux.

          • Michael dit :

            ok merci, j’attend les modifs, j’avais pas vu le fichier kernel.h avec ces fonctions.

        • Olivier Lebrun dit :

          Voila c’est corrigé, ça compile. Ça devrait fonctionner.
          N’oublie pas que si tu travailles avec un Arduino Mega tu dois (ça dépend du shield ethernet je crois) modifier les pins utilisées pour le shield ethernet dans la lib ethercard.

          • Michael dit :

            ca compil sans erreur 🙂 plus qu’a tester en live.
            Merci pour ce soutien hyper rapide.

          • Olivier Lebrun dit :

            Ok super. Il faudrait que je fasse une petite doc sur l’architecture interne de la lib pour expliquer globalement comment ça fonctionne et sur ce qui n’est pas totalement finalisé.

  2. Michael dit :

    ah j’oubliais, j’utilise aussi l’enc28J60.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *