{"id":3550,"date":"2013-04-29T08:00:37","date_gmt":"2013-04-29T07:00:37","guid":{"rendered":"http:\/\/www.blaess.fr\/christophe\/?p=3550"},"modified":"2013-04-29T08:17:05","modified_gmt":"2013-04-29T07:17:05","slug":"configuration-dune-application-embarquee","status":"publish","type":"post","link":"https:\/\/www.blaess.fr\/christophe\/2013\/04\/29\/configuration-dune-application-embarquee\/","title":{"rendered":"Configuration d&rsquo;une application embarqu\u00e9e (2\/2)"},"content":{"rendered":"<p dir=\"ltr\" style=\"text-align: justify;\"><a href=\"http:\/\/www.blaess.fr\/christophe\/2013\/03\/31\/configuration-application-embarquee\/\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-3500 alignright\" alt=\"Configuration Application Embarqu\u00e9\" src=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2013\/03\/figure-1.png\" width=\"200\" height=\"150\" \/><\/a>Cet article a \u00e9t\u00e9 \u00e9crit en collaboration avec mon ami Fran\u00e7ois Beaulier (dont je vous recommande <a title=\"http:\/\/www.ingelibre.fr\/\" href=\"http:\/\/www.ingelibre.fr\/\" target=\"_blank\">le blog<\/a>). Nous avons r\u00e9fl\u00e9chi ensemble sur les possibilit\u00e9s qui s&rsquo;offrent au d\u00e9veloppeur d&rsquo;application embarqu\u00e9e pour enregistrer le param\u00e9trage, la configuration, les pr\u00e9f\u00e9rences&#8230; bref tout ce qui constitue les donn\u00e9es persistantes de son syst\u00e8me.<\/p>\n<p dir=\"ltr\">\n<!--more-->\n<\/p>\n<p style=\"text-align: justify;\">Nous avons examin\u00e9 dans <a title=\"Configuration d\u2019une application embarqu\u00e9e (1\/2)\" href=\"http:\/\/www.blaess.fr\/christophe\/2013\/03\/31\/configuration-application-embarquee\/\"><strong>la premi\u00e8re partie de cet article<\/strong><\/a> plusieurs m\u00e9thodes de stockage des donn\u00e9es persistantes (configuration, pr\u00e9f\u00e9rences, etc.) d\u2019une application embarqu\u00e9e&nbsp;: sauvegarde binaire \u201cbrute\u201d, formatage ASCII avec fprintf(), fichier de type .ini \u00e0 la mani\u00e8re de Windows. Aucune de ces m\u00e9thodes ne nous donnent enti\u00e8rement satisfaction d\u00e8s lors qu\u2019il nous faut garantir une certaine portabilit\u00e9 entre plate-formes, et que nos donn\u00e9es peuvent \u00eatre organis\u00e9es en tableaux ou en structure arborescente.<\/p>\n<p style=\"text-align: justify;\">Dans cette seconde partie nous allons aborder des solutions plus \u00e9volu\u00e9es qui permettent de r\u00e9pondre beaucoup mieux au besoin.<\/p>\n<h1>Format sp\u00e9cifique libconfig<\/h1>\n<p style=\"text-align: justify;\">Il est int\u00e9ressant d\u2019\u00e9tudier la solution des biblioth\u00e8ques <em>open source<\/em> d\u00e9di\u00e9es \u00e0 ce probl\u00e8me des fichiers de configuration. Une des plus connues est <strong>libconfig<\/strong> qui propose un format de fichier sp\u00e9cifique de type texte avec un <em>parser<\/em> dot\u00e9 d\u2019une API simple et efficace.<\/p>\n<p style=\"text-align: justify;\">Voila ce que dit la documentation sur l\u2019objectif du projet&nbsp;:<\/p>\n<pre>------------------- &gt;\n<em>1.1 Why Another Configuration File Library?\n\nThere are several open-source configuration file libraries available as of this writing. This library was written because each of those libraries falls short in one or more ways. The main features of libconfig that set it apart from the other libraries are:\n\n    A fully reentrant parser. Independent configurations can be parsed in concurrent threads at the same time.\n\n    Both C and C++ bindings, as well as hooks to allow for the creation of wrappers in other languages.\n\n    A simple, structured configuration file format that is more readable and compact than XML and more flexible than the obsolete but prevalent Windows \u201cINI\u201d file format.\n\n    A low-footprint implementation (just 37K for the C library and 76K for the C++ library) that is suitable for memory-constrained systems.\n\n    Proper documentation.     \n\n&lt;-------------------<\/em><\/pre>\n<p style=\"text-align: justify;\">La prise en main de la biblioth\u00e8que est rapide et le format de fichier assez intuitif. La biblioth\u00e8que g\u00e8re directement les types de donn\u00e9es de base&nbsp;: <code>integer<\/code>, <code>integer64<\/code>, <code>double<\/code>, <code>boolean<\/code> et <code>string<\/code>. On peut d\u00e9finir des groupes nomm\u00e9s, comme les sections du <code>.ini<\/code>, sauf que l\u2019on peut les imbriquer. Il y a \u00e9galement une syntaxe pour d\u00e9clarer des tableaux et des listes.<\/p>\n<p style=\"text-align: justify;\">Les tableaux doivent contenir des types de base, on ne peut donc pas faire de tableaux d\u2019objets comme des structures mais par contre les listes permettent de le faire.<\/p>\n<p style=\"text-align: justify;\">Voici une mani\u00e8re de repr\u00e9senter les donn\u00e9es de notre application de domotique, en int\u00e9grant l\u2019aspect relatif \u00e0 la composition&nbsp;: les objets capteurs et actionneurs sont d\u00e9finis comme des listes \u00e0 l\u2019int\u00e9rieur de chaque pi\u00e8ce.<\/p>\n<pre># Demo application domotique\n\nversion = \"1.0\";\n\nsite:\n{\n  Nom = \"Restaurant le Marco Polo\";\n  Adresse = \"4 avenue d'Italie 75013 Paris\";\n  Pieces = (\n    {\n      Nom = \"Cuisine\";\n      Volume = 92.4;\n      Capteurs = (\n        {\n          Type = \"PT100\";\n          Identifiant = 100;\n        },\n        {\n          Type = \"KZ08\";\n          Identifiant = 101;\n        }\n      )\n      Actionneurs = (\n        {\n          Type = \"REL01\";\n          Identifiant = 200;\n        },\n        {\n          Type = \"REL01\";\n          Identifiant = 201;\n        }\n      )\n    },\n    {\n      Nom = \"Salle\";\n      Volume = 148.3;\n      Capteurs = (\n        {\n          Type = \"PT100\";\n          Identifiant = 102;\n        },\n        {\n          Type = \"AS12\";\n          Identifiant = 103;\n        },\n        {\n          Type = \"AS12\";\n          Identifiant = 104;\n        }\n      )\n      Actionneurs = (\n        {\n          Type = \"REL01\";\n          Identifiant = 202;\n        },\n        {\n          Type = \"REL01\";\n          Identifiant = 203;\n        }\n      )\n    }\n  )\n}<\/pre>\n<p style=\"text-align: justify;\">Bien s\u00fbr comme nous l\u2019avions pr\u00e9cis\u00e9 avec le format <code>.ini<\/code> ce n\u2019est pas la seule mani\u00e8re de faire, les liens de composition peuvent \u00eatre enregistr\u00e9s explicitement avec des champs suppl\u00e9mentaires dans les objets.<\/p>\n<p style=\"text-align: justify;\">Voici le code source permettant de remplir nos structures \u00e0 partir de ce fichier de configuration. La d\u00e9finition des structures a \u00e9t\u00e9 un peu revue pour avoir des cha\u00eenes de caract\u00e8res allou\u00e9es dynamiquement.<\/p>\n<pre>\/*************************************************************************\n** Demo utilisation de Libconfig\n*************************************************************************\/\n#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;string.h&gt;\n#include &lt;stdint.h&gt;\n#include \"..\/libconfig-1.4.9\/lib\/libconfig.h\"\n\n#define MAX_CAPTEURS_PAR_PIECE     10\n#define MAX_ACTIONNEURS_PAR_PIECE  10\n\n#define MAX_PIECES                 10\n\nenum capteur_t {CAPT_UNDEFINED = 0, CAPT_PT100, CAPT_KZ08, CAPT_AS12, CAPT_LAST };\nenum actionneur_t {ACT_UNDEFINED = 0, ACT_REL01, ACT_LAST };\n\nchar *capt_names[] = {\"UNDEFINED\", \"PT100\", \"KZ08\", \"AS12\"};\nchar *act_names[] = {\"UNDEFINED\", \"REL01\"};\n\nstruct s_capteur {\n   enum capteur_t type;\n   int identifiant;\n};\n\nstruct s_actionneur {\n   enum actionneur_t type;\n   int identifiant;\n};\n\nstruct s_piece{\n   char *nom;\n   double volume;\n   struct s_capteur tabCapteurs[MAX_CAPTEURS_PAR_PIECE ];\n   struct s_actionneur tabActionneurs[MAX_ACTIONNEURS_PAR_PIECE ];\n};\n\nstruct s_site {\n   char *nom;\n   char *adresse;\n   struct s_piece tabPieces[MAX_PIECES];\n};\n\nstruct s_site site_config;\nstruct config_t cfg;\n\nint main(void)\n{\n   const char *s = NULL;\n   char *version = NULL;\n   int name;\n\n   \/* Initialize the configuration *\/\n   config_init(&amp; cfg);\n\n   \/* Load the file *\/\n   printf(\"loading domotic.cfg ..\");\n   if (!config_read_file(&amp; cfg, \"domotic.cfg\")){\n     printf(\"failed : %s\\n\", config_error_text(&amp; cfg));\n     exit(1);\n   }\n   config_setting_t *cfs = NULL;\n   printf(\" ok\\n\");\n\n   \/* Get the first item at root *\/\n   if(config_lookup_string(&amp; cfg, \"version\", &amp; s) == CONFIG_TRUE)\n       version = strdup(s);\n   if(version)\n       printf(\"version = %s\\n\", version);\n\n   \/* Get the site group *\/\n   cfs = config_lookup(&amp; cfg, \"site\");\n   if(!cfs){\n       printf(\"error\\n\");\n       exit(1);\n   }\n\n   \/* Get name and address in site *\/\n   if(config_setting_lookup_string(cfs, \"Nom\", &amp; s) == CONFIG_TRUE)\n       site_config.nom = strdup(s);\n   if(config_setting_lookup_string(cfs, \"Adresse\", &amp; s) == CONFIG_TRUE)\n       site_config.adresse = strdup(s);\n\n   \/* Parse all pieces *\/\n   config_setting_t *list_pieces = config_setting_get_member(cfs, \"Pieces\");\n   int piece = 0;\n   int capt = 0;\n   int act = 0;\n   while(list_pieces) {\n       struct s_piece *pPiece = &amp; site_config.tabPieces[piece];\n       config_setting_t *pcs = config_setting_get_elem(list_pieces, piece);\n       if(!pcs)\n           break;\n       if(config_setting_lookup_string(pcs, \"Nom\", &amp; s) == CONFIG_TRUE)\n           pPiece-&gt;nom = strdup(s);\n       config_setting_lookup_float(pcs, \"Volume\", &amp; pPiece-&gt;volume);\n       \/* Parse all capteurs *\/\n       config_setting_t *list_capteurs = config_setting_get_member(pcs, \"Capteurs\");\n       capt = 0;\n       while(list_capteurs){\n           config_setting_t *cpt = config_setting_get_elem(list_capteurs, capt);\n           if(!cpt)\n               break;\n           if(config_setting_lookup_string(cpt, \"Type\", &amp; s) == CONFIG_FALSE)\n               continue;\n           for(name = 0; name &lt; CAPT_LAST; name++)\n               if(!strcmp(s, capt_names[name]))\n                   pPiece-&gt;tabCapteurs[capt].type = name;\n           config_setting_lookup_int(cpt, \"Identifiant\", &amp; pPiece-&gt;tabCapteurs[capt].identifiant);\n           capt++;\n           if(capt &gt;= MAX_CAPTEURS_PAR_PIECE)\n               break;\n       }\n       \/* Parse all actionneurs *\/\n       config_setting_t *list_actionneurs = config_setting_get_member(pcs, \"Actionneurs\");\n       act = 0;\n       while(list_actionneurs){\n           config_setting_t *ant = config_setting_get_elem(list_actionneurs, act);\n           if(!ant)\n               break;\n           if(config_setting_lookup_string(ant, \"Type\", &amp;s) == CONFIG_FALSE)\n               continue;\n           for(name = 0; name &lt; ACT_LAST; name++)\n               if(!strcmp(s, act_names[name]))\n                   pPiece-&gt;tabActionneurs[act].type = name;\n           config_setting_lookup_int(ant, \"Identifiant\", &amp;pPiece-&gt;tabActionneurs[act].identifiant);\n           act++;\n           if(act &gt;= MAX_ACTIONNEURS_PAR_PIECE)\n               break;\n       }\n       piece++;\n       if(piece &gt;= MAX_PIECES)\n           break;\n   }\n   \/* Free the configuration *\/\n   config_destroy(&amp;cfg);\n   \/* Display in-application configuration *\/\n   printf(\"Site : %s %s\\n\", site_config.nom, site_config.adresse);\n   for(piece = 0 ; piece &lt; MAX_PIECES ; piece++){\n       struct s_piece *pPiece = &amp;site_config.tabPieces[piece];\n       if(!pPiece-&gt;nom)\n           break;\n       printf(\"\\n------------piece #%d -------------\\n\", piece);\n       printf(\"%s : volume = %g\\n\", pPiece-&gt;nom, pPiece-&gt;volume);\n       for(capt = 0 ; capt &gt; MAX_CAPTEURS_PAR_PIECE; capt++){\n           if(!pPiece-&gt;tabCapteurs[capt].type)\n               break;\n           printf(\"Capteur type %s identifiant = %d\\n\", capt_names[pPiece-&gt;tabCapteurs[capt].type],\n                                                         pPiece-&gt;tabCapteurs[capt].identifiant);\n       }\n       for(act = 0 ; act &lt; MAX_ACTIONNEURS_PAR_PIECE; act++){\n           if(!pPiece-&gt;tabActionneurs[act].type)\n               break;\n           printf(\"Actionneur type %s identifiant = %d\\n\", act_names[pPiece-&gt;tabActionneurs[act].type],\n                                                           pPiece-&gt;tabActionneurs[act].identifiant);\n       }\n   }\n   return 0;\n}<\/pre>\n<p style=\"text-align: justify;\">Apr\u00e8s la suppression de l\u2019instance de la configuration on affiche le contenu des structures afin de v\u00e9rifier que tout est en place et que les objets dynamiques ont bien \u00e9t\u00e9 recopi\u00e9s.<\/p>\n<p style=\"text-align: justify;\">Au niveau de la partie <em>parsing<\/em> proprement dite on s\u2019en sort assez simplement avec deux boucles imbriqu\u00e9es, l\u2019API de la biblioth\u00e8que nous permet de parcourir les listes avec un index et donc de remplir directement nos tableaux de structures. Le fait d\u2019avoir un jeu de fonctions pour lire chaque type de donn\u00e9e directement est bien pratique.<\/p>\n<p style=\"text-align: justify;\">Nous avons l\u00e0 une solution assez \u00e9l\u00e9gante, le fichier reste modifiable manuellement tant que le nombre de niveaux d\u2019imbrication reste faible. D\u2019autre part <code>libconfig<\/code> est un projet tr\u00e8s mature dans lequel on peut avoir une assez bonne confiance.<\/p>\n<p style=\"text-align: justify; padding-left: 30px;\">Si vous voulez compiler ce code attention \u00e0 la version de <code>libconfig<\/code> disponible sur les distributions. Dans mon cas la syntaxe utilis\u00e9e n\u2019\u00e9tait pas support\u00e9e, j\u2019ai recompil\u00e9 la derni\u00e8re version propos\u00e9e sur le site. Inutile d\u2019installer la biblioth\u00e8que et de risquer de perturber les d\u00e9pendances de votre distribution, faites juste une compilation puis utilisez la biblioth\u00e8que statique <code>libconfig.a<\/code> en l\u2019ajoutant \u00e0 la ligne de commande de gcc&nbsp;:<\/p>\n<pre style=\"padding-left: 30px;\">gcc -O2 -Wall -o domo domo.c ..\/libconfig-1.4.9\/lib\/.libs\/libconfig.a<\/pre>\n<p style=\"padding-left: 30px; text-align: justify;\">Et plutot que d\u2019inclure un <code>&lt;libconfig.h&gt;<\/code> qui risque d\u2019\u00eatre incompatible, utilisez un chemin en dur&nbsp;:<\/p>\n<pre style=\"padding-left: 30px;\">  #include \"..\/libconfig-1.4.9\/lib\/libconfig.h\"<\/pre>\n<p style=\"padding-left: 30px; text-align: justify;\">Pour \u00eatre certain d\u2019avoir le bon.<\/p>\n<h1>Format structur\u00e9 arborescent Xml<\/h1>\n<p style=\"text-align: justify;\">Le Xml est une solution que j\u2019ai utilis\u00e9e avec succ\u00e8s pendant des ann\u00e9es sur des projets Linux embarqu\u00e9. Au d\u00e9part mon id\u00e9e \u00e9tait simplement de trouver \u201cquelque chose de standard\u201d pour stocker ma configuration. J&rsquo;esp\u00e9rais \u00e9galement pouvoir profiter de la disponibilit\u00e9 d\u2019outils existants pour \u00e9diter et v\u00e9rifier mes fichiers.<\/p>\n<p style=\"text-align: justify;\">Le Xml est assez d\u00e9routant au d\u00e9but car tout ce qui tourne autour de cette technologie est tr\u00e8s abstrait et pas forc\u00e9ment attirant pour les programmeurs du monde embarqu\u00e9. Pourtant si l\u2019on se contente des principes de base on peut avoir facilement quelque chose de tr\u00e8s efficace.<\/p>\n<p style=\"text-align: justify;\">La lecture de fichiers Xml est quelque chose d\u2019hyper-standard et la plupart des langages ont une API native pour le faire. Il existe deux types d\u2019API pour acc\u00e9der au Xml, qui correspondent a deux principes fondamentaux diff\u00e9rent pour le parsing du fichier:<\/p>\n<ul>\n<li style=\"text-align: justify;\">le SAX (<em>simple API for Xml<\/em>) repose sur un principe asynchrone d\u2019appel de handlers. Le <em>parser<\/em> parcourt tout le fichier Xml et appelle les handlers que l\u2019on a d\u00e9clar\u00e9s pour chaque balise rencontr\u00e9e. Cette API est particuli\u00e8rement adapt\u00e9e \u00e0 la lecture de documents volumineux car elle \u00e9vite de charger tout le contenu en m\u00e9moire.<\/li>\n<li style=\"text-align: justify;\">le DOM (<em>Document Object Model<\/em>) en revanche charge l\u2019int\u00e9gralit\u00e9 du document sous la forme d\u2019un arbre en m\u00e9moire. Ensuite des fonctions permettent de parcourir, rechercher, lire, \u00e9crire et cr\u00e9er des n\u0153uds.<\/li>\n<\/ul>\n<p style=\"text-align: justify;\">Bien que ces deux principes soient des standards il n\u2019existe pas \u00e0 ma connaissance de standard sur l\u2019API elle m\u00eame et chaque impl\u00e9mentation aura ses particularit\u00e9s.<\/p>\n<p style=\"text-align: justify;\">Une biblioth\u00e8que tr\u00e8s bien faite, simple et de petite taille, pour la lecture et l\u2019\u00e9criture de fichiers Xml depuis une application est <strong>Mini-Xml<\/strong>. Elle est en g\u00e9n\u00e9ral disponible sur les distributions sous le nom de <code>libmxml<\/code>, sachant que pour l\u2019utiliser en d\u00e9veloppement il faudra installer le paquet <code>libmxml-dev<\/code>. L\u2019API propos\u00e9e est de type DOM et de nombreuses fonctions permettent de rechercher ou parcourir l\u2019arbre.<\/p>\n<p style=\"text-align: justify;\">Voyons \u00e0 quoi peut ressembler notre fichier de configuration:<\/p>\n<pre>&lt;?xml version = '1.0' encoding = 'UTF-8'?&gt;\n&lt;Root_Element&gt;\n  &lt;Version&gt;1.0&lt;\/Version&gt;\n  &lt;Site&gt;\n    &lt;Nom&gt;Restaurant le Marco Polo&lt;\/Nom&gt;\n    &lt;Adresse&gt;4 avenue d'Italie 75013 Paris&lt;\/Adresse&gt;\n    &lt;Piece&gt;\n      &lt;Nom&gt;Cuisine&lt;\/Nom&gt;\n      &lt;Volume&gt;92.4&lt;\/Volume&gt;\n      &lt;Capteur&gt;\n        &lt;Type&gt;PT100&lt;\/Type&gt;\n        &lt;Identifiant&gt;100&lt;\/Identifiant&gt;\n      &lt;\/Capteur&gt;\n      &lt;Capteur&gt;\n        &lt;Type&gt;KZ08&lt;\/Type&gt;\n        &lt;Identifiant&gt;101&lt;\/Identifiant&gt;\n      &lt;\/Capteur&gt;\n      &lt;Actionneur&gt;\n        &lt;Type&gt;REL01&lt;\/Type&gt;\n        &lt;Identifiant&gt;200&lt;\/Identifiant&gt;\n      &lt;\/Actionneur&gt;\n      &lt;Actionneur&gt;\n        &lt;Type&gt;REL01&lt;\/Type&gt;\n        &lt;Identifiant&gt;201&lt;\/Identifiant&gt;\n      &lt;\/Actionneur&gt;\n    &lt;\/Piece&gt;\n    &lt;Piece&gt;\n      &lt;Nom&gt;Salle&lt;\/Nom&gt;\n      &lt;Volume&gt;148.3&lt;\/Volume&gt;\n      &lt;Capteur&gt;\n        &lt;Type&gt;PT100&lt;\/Type&gt;\n        &lt;Identifiant&gt;102&lt;\/Identifiant&gt;\n      &lt;\/Capteur&gt;\n      &lt;Capteur&gt;\n        &lt;Type&gt;AS12&lt;\/Type&gt;\n        &lt;Identifiant&gt;103&lt;\/Identifiant&gt;\n      &lt;\/Capteur&gt;\n      &lt;Capteur&gt;\n        &lt;Type&gt;AS12&lt;\/Type&gt;\n        &lt;Identifiant&gt;104&lt;\/Identifiant&gt;\n      &lt;\/Capteur&gt;\n      &lt;Actionneur&gt;\n        &lt;Type&gt;REL01&lt;\/Type&gt;\n        &lt;Identifiant&gt;202&lt;\/Identifiant&gt;\n      &lt;\/Actionneur&gt;\n      &lt;Actionneur&gt;\n        &lt;Type&gt;REL01&lt;\/Type&gt;\n        &lt;Identifiant&gt;203&lt;\/Identifiant&gt;\n      &lt;\/Actionneur&gt;\n    &lt;\/Piece&gt;\n  &lt;\/Site&gt;\n&lt;\/Root_Element&gt;<\/pre>\n<p style=\"text-align: justify;\">Effectivement par rapport \u00e0 <code>libconfig<\/code> la syntaxe est plus bavarde puisque chaque nom de balise est r\u00e9p\u00e9t\u00e9 deux fois. Ceci dit la structure m&rsquo;appara\u00eet plus claire et plus lisible, on identifie tr\u00e8s bien les diff\u00e9rentes branches et une modification manuelle est facilement faisable.<\/p>\n<p style=\"text-align: justify;\">Premier avantage du Xml, si je souhaite v\u00e9rifier que mon fichier est bien form\u00e9 au sens Xml, c\u2019est \u00e0 dire que je n\u2019ai pas oubli\u00e9 une balise fermante par exemple, il existe des outils tout pr\u00eats. Par exemple <code>rxp<\/code> est un petit outil de validation en ligne de commande&nbsp;:<\/p>\n<pre>$ <strong>rxp -s config.xml<\/strong><\/pre>\n<p style=\"text-align: justify;\">Si aucun message d\u2019erreur, mon fichier est correct. Enlevons par exemple la derni\u00e8re balise <code>&lt;\/Actionneur&gt;<\/code>.<\/p>\n<pre>$ <strong>rxp -s config.xml<\/strong>\nError: Mismatched end tag: expected &lt;\/Actionneur&gt;, got &lt;\/Piece&gt;\nin unnamed entity at line 49 char 10 of file:\/\/\/home\/francois\/domotic-xml\/config.xml<\/pre>\n<p>Et voila l\u2019erreur signal\u00e9e&nbsp;!<\/p>\n<p style=\"text-align: justify;\">On pourrait \u00e9galement \u00e9crire un fichier de document type <a title=\"http:\/\/fr.wikipedia.org\/wiki\/DTD\" href=\"http:\/\/fr.wikipedia.org\/wiki\/DTD\" target=\"_blank\">d\u00e9finition DTD<\/a> qui permettrais \u00e0 <code>rxp<\/code> de v\u00e9rifier beaucoup plus finement le contenu du fichier.<\/p>\n<p style=\"text-align: justify;\">Pour la lecture du fichier le tout est de bien comprendre les fonctions de l\u2019API de <em>Mini-Xml<\/em>. Il y a un petit tutoriel tr\u00e8s utile sur le site. Ensuite il reste un cot\u00e9 un peu fastidieux par le fait de tester le nom de chaque balise et de faire la conversion qui s\u2019impose vers le bon type de variable. C\u2019est la que <code>libconfig<\/code> offre un avantage par le fait de proposer des fonction de conversions.<\/p>\n<pre>#include &lt;stdlib.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;string.h&gt;\n#include &lt;mxml.h&gt;\n\n#define MAX_CAPTEURS_PAR_PIECE 10\n#define MAX_ACTIONNEURS_PAR_PIECE 10\n#define MAX_PIECES 10\n\nenum capteur_t {CAPT_UNDEFINED = 0, CAPT_PT100, CAPT_KZ08, CAPT_AS12, CAPT_LAST };\nenum actionneur_t {ACT_UNDEFINED = 0, ACT_REL01, ACT_LAST };\n\nchar *capt_names[] = {\"UNDEFINED\", \"PT100\", \"KZ08\", \"AS12\"};\nchar *act_names[] = {\"UNDEFINED\", \"REL01\"};\n\nstruct s_capteur {\n   enum capteur_t type;\n   int identifiant;\n};\n\nstruct s_actionneur {\n   enum actionneur_t type;\n   int identifiant;\n};\n\nstruct s_piece{\n   char *nom;\n   double volume;\n   struct s_capteur tabCapteurs[MAX_CAPTEURS_PAR_PIECE ];\n   struct s_actionneur tabActionneurs[MAX_ACTIONNEURS_PAR_PIECE ];\n};\n\nstruct s_site {\n   char *nom;\n   char *adresse;\n   struct s_piece tabPieces[MAX_PIECES];\n};\n\nstruct s_site site_config = {0};\nchar *version = NULL;\n\n\/*\n * Utilise mini-xml pour charger dans un arbre en memoire le contenu du fichier FileName\n * FileName : chemin du fichier \u00e0 ouvrir\n * return : Pointeur sur le noeud racine de l'arbre\n *\/\nmxml_node_t *loadXmlTree(const char *FileName)\n{\n   FILE *fp;\n   mxml_node_t *tree;\n\n   fp = fopen(FileName, \"r\");\n   if(fp == NULL) {\n      printf(\"Erreur ouverture en lecture %s :\\n\",FileName);\n      perror(\"fopen\");\n      return NULL;\n   }\n   tree = mxmlLoadFile(NULL, fp, MXML_OPAQUE_CALLBACK);\n   if(tree == NULL)\n      printf(\"Erreur lecture xml %s\\n\",FileName);\n   fclose(fp);\n   return tree;\n}\n\nvoid parseActionneur(mxml_node_t *node, struct s_actionneur *pAct)\n{\n   char *SubValue, *SubTag = NULL;\n   mxml_node_t *fils=node-&gt;child;\n   int name;\n\n   while(fils != NULL) {\n      if ((fils-&gt;type != MXML_ELEMENT)\n       ||(fils-&gt;child == NULL)) {\n         fils = fils-&gt;next;\n         continue;\n      }\n      SubValue = fils-&gt;child-&gt;value.opaque;\n      SubTag=fils-&gt;value.element.name;\n      if (strcmp (SubTag, \"Type\") == 0){\n         for(name = 0; name &lt; ACT_LAST; name++)\n         if(!strcmp(SubValue, act_names[name]))\n            pAct-&gt;type = name;\n      } else if (strcmp (SubTag, \"Identifiant\") == 0) {\n         pAct-&gt;identifiant = strtol(SubValue, NULL, 10);\n      }\n      fils=fils-&gt;next;\n   }\n}\n\nvoid parseCapteur(mxml_node_t *node, struct s_capteur *pCapt)\n{\n   char *SubValue, *SubTag=NULL;\n   mxml_node_t *fils=node-&gt;child;\n   int name;\n\n   while(fils != NULL) {\n      if ((fils-&gt;type != MXML_ELEMENT)\n       ||(fils-&gt;child == NULL)) {\n         fils = fils-&gt;next;\n         continue;\n      }\n      SubValue=fils-&gt;child-&gt;value.opaque;\n      SubTag=fils-&gt;value.element.name;\n\n      if (strcmp (SubTag, \"Type\") == 0){\n         for(name = 0; name &lt; CAPT_LAST; name++)\n         if (! strcmp(SubValue, capt_names[name]))\n            pCapt-&gt;type = name;\n\n      } else if (strcmp (SubTag, \"Identifiant\") == 0) {\n         pCapt-&gt;identifiant = strtol(SubValue, NULL, 10);\n      }\n      fils=fils-&gt;next;\n   }\n}\n\nvoid parsePiece(mxml_node_t *node, struct s_piece *pPiece)\n{\n   char *SubValue, *SubTag=NULL;\n   mxml_node_t *fils=node-&gt;child;\n   int IdxCapt = 0;\n   int IdxAct = 0;\n   while(fils != NULL) {\n      if ((fils-&gt;type != MXML_ELEMENT)\n       ||(fils-&gt;child == NULL)) {\n         fils=fils-&gt;next;\n         continue;\n      }\n      SubValue=fils-&gt;child-&gt;value.opaque;\n      SubTag=fils-&gt;value.element.name;\n      if (strcmp (SubTag, \"Nom\") == 0)\n         pPiece-&gt;nom = strdup(SubValue);\n      else if (strcmp (SubTag, \"Volume\") == 0)\n         pPiece-&gt;volume = strtod(SubValue, NULL);\n      else if (strcmp (SubTag, \"Capteur\") == 0)\n         parseCapteur(fils, &amp;pPiece-&gt;tabCapteurs[IdxCapt++]);\n      else if (strcmp (SubTag, \"Actionneur\") == 0)\n         parseActionneur(fils, &amp;pPiece-&gt;tabActionneurs[IdxAct++]);\n      fils=fils-&gt;next;\n   }\n}\n\nvoid parseSite(mxml_node_t *node, struct s_site *pSite)\n{\n   char *SubValue, *SubTag=NULL;\n   mxml_node_t *fils=node-&gt;child;\n   int IdxPiece = 0;\n\n   while(fils != NULL) {\n      if ((fils-&gt;type != MXML_ELEMENT)\n       ||(fils-&gt;child == NULL)) {\n         fils = fils-&gt;next;\n         continue;\n      }\n\n      SubValue=fils-&gt;child-&gt;value.opaque;\n      SubTag=fils-&gt;value.element.name;\n\n      if (strcmp (SubTag, \"Nom\") == 0)\n         site_config.nom = strdup(SubValue);\n      else if (strcmp (SubTag, \"Adresse\") == 0)\n         site_config.adresse = strdup(SubValue);\n      else if (strcmp (SubTag, \"Piece\") == 0)\n         parsePiece(fils, &amp;pSite-&gt;tabPieces[IdxPiece++]);\n      fils=fils-&gt;next;\n   }\n}\n\nvoid parseConfig(mxml_node_t *tree, struct s_site *pSite)\n{\n   char *SubValue, *SubTag=NULL;\n\n   mxml_node_t *node = mxmlFindElement(tree, tree, \"Root_Element\", NULL, NULL, MXML_DESCEND);\n   mxml_node_t *fils = node-&gt;child;\n\n   while (fils != NULL) {\n      if ((fils-&gt;type != MXML_ELEMENT)\n       ||(fils-&gt;child == NULL)) {\n         fils = fils-&gt;next;\n         continue;\n      }\n\n      SubValue=fils-&gt;child-&gt;value.opaque;\n      SubTag=fils-&gt;value.element.name;\n      if(strcmp(SubTag,\"Version\") == 0)\n         version = strdup(SubValue);\n      else if(strcmp(SubTag,\"Site\") == 0)\n         parseSite(fils, pSite);\n      fils=fils-&gt;next;\n   }\n}\n\nint main(void)\n{\n   int piece, capt, act;\n   mxml_node_t *tree = loadXmlTree(\"domotic.xml\");\n\n   if(tree == NULL)\n      return -1;\n\n   parseConfig(tree, &amp;site_config);\n   mxmlDelete(tree);\n\n   \/* Display in-application configuration *\/\n   printf(\"Site : %s %s\\n\", site_config.nom, site_config.adresse);\n   for(piece = 0 ; piece &lt; MAX_PIECES ; piece++){\n      struct s_piece *pPiece = &amp;site_config.tabPieces[piece];\n      if(!pPiece-&gt;nom)\n         break;\n      printf(\"\\n------------piece #%d -------------\\n\", piece);\n      printf(\"%s : volume = %g\\n\", pPiece-&gt;nom, pPiece-&gt;volume);\n      for(capt = 0 ; capt &lt; MAX_CAPTEURS_PAR_PIECE; capt++){\n         if(! pPiece-&gt;tabCapteurs[capt].type)\n            break;\n         printf(\"Capteur type %s identifiant = %d\\n\", capt_names[pPiece-&gt;tabCapteurs[capt].type],\n         pPiece-&gt;tabCapteurs[capt].identifiant);\n      }\n      for(act = 0 ; act &lt; MAX_ACTIONNEURS_PAR_PIECE; act++){\n         if(! pPiece-&gt;tabActionneurs[act].type)\n            break;\n         printf(\"Actionneur type %s identifiant = %d\\n\", act_names[pPiece-&gt;tabActionneurs[act].type],\n         pPiece-&gt;tabActionneurs[act].identifiant);\n      }\n   }\n   return 0;\n}<\/pre>\n<h1>Base de donn\u00e9es<\/h1>\n<p style=\"text-align: justify;\">L\u2019emploi d\u2019une base de donn\u00e9es peut para\u00eetre a priori d\u00e9mesur\u00e9 pour enregistrer la configuration d\u2019un syst\u00e8me embarqu\u00e9 car cela \u00e9voque imm\u00e9diatement la notion de serveur \u00e0 l\u2019administration assez lourde (<em>PostgreSQL<\/em>, <em>MySQL\/MariaDB<\/em>, <em>Oracle<\/em>,&#8230;). Il existe toutefois des impl\u00e9mentations tr\u00e8s simples, pr\u00e9vues pour l\u2019embarqu\u00e9. La plus r\u00e9pandue dans ce domaine est <strong>SQLite<\/strong>, que nous allons examiner ici.<\/p>\n<p style=\"text-align: justify;\"><em>SQLite<\/em> est en r\u00e9alit\u00e9 une biblioth\u00e8que capable de g\u00e9rer directement les fichiers de la base de donn\u00e9es sans passer par un serveur distinct. Lorsque nous lions notre application avec cette biblioth\u00e8que, cela lui permet d\u2019utiliser la base de donn\u00e9es de mani\u00e8re autonome. Ce m\u00e9canisme est naturellement limit\u00e9 en terme de performances, surtout lors d\u2019acc\u00e8s en parall\u00e8le depuis plusieurs applications, mais le but de <em>SQLite<\/em> n\u2019est pas d\u2019\u00eatre une alternative aux SGBD classiques g\u00e9rant des bases volumineuses, mais plut\u00f4t de repr\u00e9senter une solution portable, \u00e9l\u00e9gante et facile \u00e0 maintenir pour enregistrer des \u00e9l\u00e9ments de configuration.<\/p>\n<h2>Acc\u00e8s depuis le shell<\/h2>\n<p style=\"text-align: justify;\">Il existe un petit utilitaire nomm\u00e9 <code>sqlite3<\/code> qui accepte des requ\u00eates SQL sur son entr\u00e9e standard (ou sa ligne de commande) et les applique \u00e0 la base de donn\u00e9es indiqu\u00e9e en argument. Sans redirection de son entr\u00e9e-standard, il pr\u00e9sente une interface interactive simple. Voici un exemple dans lequel nous allons utiliser un script shell qui cr\u00e9era une base de donn\u00e9es. Pour cela nous employons une syntaxe avec un \u201c<em><a title=\"http:\/\/fr.wikipedia.org\/wiki\/Here_Document\" href=\"http:\/\/fr.wikipedia.org\/wiki\/Here_Document\" target=\"_blank\">Here Document<\/a><\/em>\u201d comme c\u2019est souvent l\u2019usage.<\/p>\n<pre><a title=\"creation-bdd.sh\" href=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2013-04-28\/creation-bdd.sh\" target=\"_blank\"><strong>creation-bdd.sh:<\/strong><\/a>\n#! \/bin\/sh\n\nBDD=.\/domotique.sql\n\nrm -f \"${BDD}\" || { echo \"impossible d'effacer ${BDD}.\"; exit 1; }\n\ntouch \"${BDD}\" || { echo \"impossible de creer ${BDD}.\";  exit 1; }\n\nsqlite3 \"${BDD}\" &lt;&lt; FIN_SITE\n       CREATE TABLE site (\n           nom     CHAR(64),\n           adresse CHAR(512),\n           ref     INTEGER PRIMARY KEY\n       );\nFIN_SITE\n\nsqlite3 \"${BDD}\" &lt;&lt; FIN_PIECE\n       CREATE TABLE piece (\n           nom      CHAR(256),\n           volume   FLOAT,\n           ref_site INTEGER,\n           ref      INTEGER PRIMARY KEY,\n           FOREIGN KEY (ref_site) REFERENCES site(ref)\n       );\nFIN_PIECE\n\nsqlite3 \"${BDD}\" &lt;&lt; FIN_CAPTEUR\n       CREATE TABLE capteur (\n           type        CHAR(32),\n           identifiant CHAR(32),\n           ref_piece   INTEGER,\n           FOREIGN KEY(ref_piece) REFERENCES piece(ref)\n           PRIMARY KEY(ref_piece, identifiant)\n       );\nFIN_CAPTEUR\n\nsqlite3 \"${BDD}\" &lt;&lt; FIN_ACTIONNEUR\n       CREATE TABLE actionneur (\n           type        CHAR(32),\n           identifiant CHAR(32),\n           ref_piece   INTEGER,\n           FOREIGN KEY(ref_piece) REFERENCES piece(ref)\n           PRIMARY KEY(ref_piece, identifiant)\n       );\nFIN_ACTIONNEUR<\/pre>\n<p style=\"text-align: justify;\">Ce script commence par effacer l\u2019\u00e9ventuelle base de donn\u00e9es pr\u00e9c\u00e9dente avant de la reconstruire (sous forme d\u2019un unique fichier <code>domotique.sql<\/code>) en y cr\u00e9ant quatre tables&nbsp;: une pour le(s) site(s) supervis\u00e9(s), une pour les pi\u00e8ces se trouvant dans ce site, et deux pour les capteurs et actionneurs disponibles dans chaque pi\u00e8ce. La structure des enregistrements est encore arborescente, car chaque pi\u00e8ce dispose d\u2019un champ \u201c<code>ref_site<\/code>\u201d indiquant le num\u00e9ro de r\u00e9f\u00e9rence du chantier auquel elle appartient. De m\u00eame les capteurs et actionneurs ont des champs \u201c<code>ref_piece<\/code>\u201c pour conna\u00eetre leurs emplacements. Ce script a un comportement plut\u00f4t strict car il efface toutes les donn\u00e9es pr\u00e9c\u00e9dentes. On peut l\u2019assimiler \u00e0 une fonctionnalit\u00e9 \u201c<em>Retour aux param\u00e8tres d\u2019usine<\/em>\u201d pr\u00e9sente sur la plupart des syst\u00e8mes embarqu\u00e9s.<\/p>\n<p style=\"text-align: justify;\">La construction des tables est un peu compliqu\u00e9e par l\u2019usage des contraintes <code>FOREIGN KEY<\/code> et <code>PRIMARY KEY<\/code>. L\u2019objectif est de garantir l\u2019unicit\u00e9 de chaque site par son num\u00e9ro de r\u00e9f\u00e9rence, puis de chaque pi\u00e8ce (par son num\u00e9ro de r\u00e9f\u00e9rence \u00e9galement) et enfin de chaque capteur en combinant num\u00e9ro de pi\u00e8ce et identifiant de capteur &#8211; et de chaque actionneur suivant le m\u00eame principe. La notion de <code>FOREIGN KEY<\/code> permet de s\u2019assurer que le champ <code>ref_site<\/code> d\u2019une pi\u00e8ce corresponde bien avec un site existant dans la base (et de m\u00eame pour les champs <code>ref_piece<\/code> des capteurs et actionneurs).<\/p>\n<p style=\"text-align: justify; padding-left: 30px;\">Attention&nbsp;: par d\u00e9faut <em>SQLite3<\/em> n\u2019effectue pas de v\u00e9rification de coh\u00e9rence sur les cl\u00e9s <code>FOREIGN<\/code>. Pour activer cette v\u00e9rification il faudra ajouter une directive \u00ab\u00a0<code>PRAGMA foreign_keys=ON<\/code>\u201d lors de l\u2019insertion de nouveaux enregistrements.<\/p>\n<p style=\"text-align: justify;\">Nous pouvons v\u00e9rifier la composition de nos tables directement depuis le shell en utilisant la commande \u201c<code>.schema<\/code>\u201d de <code>sqlite3<\/code>.<\/p>\n<pre>$ <strong>sqlite3 domotique.sql .schema<\/strong>\nCREATE TABLE actionneur (\n       type        CHAR(32),\n       identifiant CHAR(32),\n       ref_piece   INTEGER,\n       FOREIGN KEY(ref_piece) REFERENCES piece(ref)\n       PRIMARY KEY(ref_piece, identifiant)\n   );\n\nCREATE TABLE capteur (\n       type        CHAR(32),\n       identifiant CHAR(32),\n       ref_piece   INTEGER,\n       FOREIGN KEY(ref_piece) REFERENCES piece(ref)\n       PRIMARY KEY(ref_piece, identifiant)\n   );\n\nCREATE TABLE piece (\n       nom         CHAR(256),\n       volume   FLOAT,\n       ref_site INTEGER,\n       ref      INTEGER PRIMARY KEY,\n       FOREIGN KEY (ref_site) REFERENCES site(ref)\n   );\n\nCREATE TABLE site (\n       nom     CHAR(64),\n       adresse CHAR(512),\n       ref     INTEGER PRIMARY KEY\n   );\n$<\/pre>\n<p style=\"text-align: justify;\">Nous allons commencer \u00e0 remplir nos tables. Pour cr\u00e9er un premier site avec deux pi\u00e8ces je vais utiliser \u00e0 nouveau un petit script shell. On pourrait imaginer que ce script soit t\u00e9l\u00e9charg\u00e9 sur les syst\u00e8mes embarqu\u00e9s lors d\u2019une mise \u00e0 jour, en transf\u00e9rant un nouveau <em>firmware<\/em> par exemple.<\/p>\n<pre><strong><a title=\"initialisation-table.sh\" href=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2013-04-28\/initialisation-table.sh\" target=\"_blank\">initialisation-table.sh<\/a>:<\/strong>\n#! \/bin\/sh\n\nBDD=.\/domotique.sql\n\nsqlite3 \"${BDD}\" &lt;&lt; FIN_INITIALISATION\n   PRAGMA foreign_keys=ON;\n   INSERT INTO site VALUES    (\n       \"Restaurant le Marco Polo\",\n       \"4, avenue d'Italie - 75013 Paris\",\n       1\n   );\n\n   INSERT INTO piece (nom, volume, ref_site) VALUES (\n       \"Cuisine\",\n        92.4,\n        1\n   );\n\n   INSERT INTO piece (nom, volume, ref_site) VALUES (\n       \"Salle\",\n       148.3,\n       1\n   );\nFIN_INITIALISATION<\/pre>\n<p style=\"text-align: justify;\">Pour l\u2019enregistrement du site, nous avons rempli tous les champs, y compris le num\u00e9ro de r\u00e9f\u00e9rence, car il est utilis\u00e9 explicitement lors de la cr\u00e9ation des pi\u00e8ces. Sachez que <em>SQLite<\/em> peut parfaitement fournir des valeurs enti\u00e8res uniques lorsque la cl\u00e9 primaire n\u2019est pas indiqu\u00e9e. C\u2019est ce qui se passe pour l\u2019enregistrement des deux pi\u00e8ces o\u00f9 nous n\u2019avons pas fourni de valeurs pour le champ \u201c<code>ref<\/code>\u201d (nous verrons les valeurs attribu\u00e9es un peu plus loin).<\/p>\n<p style=\"text-align: justify;\">Pour la saisie des capteurs, nous allons changer de langage. Je vais utiliser un script PHP pour cr\u00e9er les capteurs. Ceci est assez repr\u00e9sentatif d\u2019un syst\u00e8me embarqu\u00e9 sur lequel le param\u00e9trage est r\u00e9alis\u00e9 \u00e0 partir d\u2019une petite interface HTML (avec un serveur HTTP embarqu\u00e9 comme <em>Lighttpd<\/em>).<\/p>\n<p style=\"text-align: justify;\">Voici un script PHP qui attend sur sa ligne de commande le nom du site et celui de la pi\u00e8ce concern\u00e9e, puis il ajoute un capteur du type indiqu\u00e9 avec le num\u00e9ro de r\u00e9f\u00e9rence correspondant (bien entendu s\u2019il \u00e9tait appel\u00e9 par un serveur HTTP, les param\u00e8tres seraient dans <code>$_GET[\u00a0]<\/code> ou <code>$POST[\u00a0]<\/code> par exemple).<\/p>\n<pre><strong><a title=\"insertion-capteur.php\" href=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2013-04-28\/insertion-capteur-php.txt\" target=\"_blank\">insertion-capteur.php<\/a>:<\/strong>\n&lt;?php\n       if ($argc != 5) {\n               die(\"usage: $argv[0] &lt;nom_site&gt; &amp;lt,nom_piece&gt; &lt;type_capteur&gt; &lt;identifiant_capteur&gt;\\n\");\n       }\n\n       $nom_site  = \"$argv[1]\";\n       $nom_piece = \"$argv[2]\";\n       $type_capteur = \"$argv[3]\";\n       $identifiant_capteur = \"$argv[4]\";\n\n       $db = new SQLite3(\"domotique.sql\");\n       $result = $db-&gt;query('SELECT * FROM site WHERE nom=\"'.$nom_site.'\";');\n       if ($result == null) {\n               die(\"Erreur dans l'acc\u00e8s \u00e0 la base de donn\u00e9es\\n\");\n       }\n\n       $line = $result-&gt;fetchArray();\n       if ($line == null) {\n               die(\"Impossible de trouver le site : $nom_site\\n\");\n       }\n\n       $ref_site = $line['ref'];\n       $result = $db-&gt;query('SELECT * FROM piece WHERE nom=\"'.$nom_piece.'\" AND ref_site=\"'.$ref_site.'\";');\n       if ($result == null) {\n               die(\"Erreur dans l'acc\u00e8s \u00e0 la base de donn\u00e9es\\n\");\n       }\n\n       $line = $result-&gt;fetchArray();\n       if ($line == null) {\n               die(\"Impossible de trouver la piece : $nom_piece\\n\");\n       }\n\n       $ref_piece = $line['ref'];\n       $db-&gt;query(\"INSERT INTO capteur VALUES ('\".$type_capteur.\"','\".$identifiant_capteur.\"','\".$ref_piece.\"')\");\n?&gt;<\/pre>\n<p style=\"text-align: justify; padding-left: 30px;\">\u00c0 noter&nbsp;: nous avons utilis\u00e9 ici le module <em>SQLite3<\/em> de PHP. Nous pourrions tr\u00e8s bien acc\u00e9der \u00e0 notre base par le module PDO (<em>PHP Data Object<\/em>) qui est une interface d\u2019abstraction de plus haut niveau. J\u2019ai une pr\u00e9f\u00e9rence pour <em>SQLite3<\/em> dans le contexte embarqu\u00e9 car l\u2019API est plus simple, en outre elle nous permet d\u2019ouvrir une base de donn\u00e9es en lecture-seule (option tr\u00e8s utile sur les syst\u00e8mes embarqu\u00e9s o\u00f9 l\u2019alimentation \u00e9lectrique peut \u00eatre coup\u00e9e intempestivement), ce qui n\u2019est \u00e0 ma connaissance pas possible avec PDO.<\/p>\n<p style=\"text-align: justify;\">Ins\u00e9rons deux capteurs dans la cuisine de notre restaurant&nbsp;:<\/p>\n<pre>$ <strong>php insertion-capteur.php \"Restaurant le Marco Polo\" \"Cuisine\" \"PT100\" \"100\"<\/strong>\n$ <strong>php insertion-capteur.php \"Restaurant le Marco Polo\" \"Cuisine\" \"KZ08\" \"101\"<\/strong><\/pre>\n<p>Nous pouvons appeler <code>sqlite3<\/code> directement pour v\u00e9rifier l\u2019insertion des capteurs&nbsp;:<\/p>\n<pre>$ <strong>sqlite3 domotique.sql \"SELECT * FROM capteur;\"<\/strong>\nPT100|100|1\nKZ08|101|1\n$<\/pre>\n<p style=\"text-align: justify;\">La biblioth\u00e8que <em>SQLite3<\/em> est \u00e9videmment accessible depuis le langage C par exemple. Voici un petit exemple de programme qui va consulter et afficher la liste des capteurs du site et de la pi\u00e8ce dont on lui passe les noms en argument.<\/p>\n<pre><strong><a title=\"affiche-contenu-site.c\" href=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2013-04-28\/affiche-contenu-site.c\" target=\"_blank\">affiche-contenu-site.c<\/a>:<\/strong>\n#include &lt;unistd.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;string.h&gt;\n#include &lt;sqlite3.h&gt;\n\nstatic int callback_read_int(void * param, int nb_fields, char * value[], char * field[]);\nstatic int callback_capteur(void * unused, int nb_fields, char * value[], char * field[]);\n\nint main(int argc, char *argv[])\n{\n   sqlite3 * db;\n   int ref_site = -1;\n   int ref_piece = -1;\n   char requete[256];\n   if (argc != 4) {\n       fprintf (stderr, \"usage: %s   \\n\", argv[0]);\n       exit(EXIT_FAILURE);\n   }\n\n   if (sqlite3_open_v2(argv[1], &amp; db, SQLITE_OPEN_READONLY, NULL) != 0) {\n       fprintf(stderr, \"%s: impossible d'ouvrir la base %s\\n\", argv[0], argv[1]);\n       exit(EXIT_FAILURE);\n   }\n\n   \/\/ Rechercher le numero de reference du site correspondant au nom indique.\n   snprintf(requete, 256, \"SELECT ref FROM site WHERE nom='%s'\", argv[2]);\n   if (sqlite3_exec(db, requete, callback_read_int, &amp;ref_site, NULL) != 0) {\n       fprintf(stderr, \"%s: erreur dans la base %s\\n\", argv[0], argv[1]);\n       sqlite3_close(db);\n       exit(EXIT_FAILURE);\n   }\n\n   if (ref_site == -1) {\n       fprintf(stderr, \"%s: Site %s 'non trouve'\\n\", argv[0], argv[2]);\n       sqlite3_close(db);\n       exit(EXIT_FAILURE);\n   }\n\n   printf(\"%s\\n\", argv[2]);\n   \/\/ Rechercher le numero de reference de la piece correspondant au nom indique.\n   snprintf(requete, 256, \"SELECT ref FROM piece WHERE ref_site='%d' AND nom='%s'\", ref_site, argv[3]);\n   if (sqlite3_exec(db, requete, callback_read_int, &amp;ref_piece, NULL) != 0) {\n       fprintf(stderr, \"%s: erreur dans la base %s\\n\", argv[0], argv[1]);\n       sqlite3_close(db);\n       exit(EXIT_FAILURE);\n   }\n\n   if (ref_piece == -1) {\n       fprintf(stderr, \"%s: Piece %s 'non trouvee'\\n\", argv[0], argv[3]);\n       sqlite3_close(db);\n       exit(EXIT_FAILURE);\n   }\n\n   printf(\"  %s\\n\", argv[3]);\n\n   \/\/ Parcourir les capteurs de la piece selectionnee.\n   snprintf(requete, 256, \"SELECT * FROM capteur WHERE ref_piece='%d'\", ref_piece);\n   if (sqlite3_exec(db, requete, callback_capteur, NULL, NULL) != 0) {\n       fprintf(stderr, \"%s: Pas de capteur trouve\\n\", argv[0]);\n       sqlite3_close(db);\n       exit(EXIT_FAILURE);\n   }\n\n   sqlite3_close(db);\n   return EXIT_SUCCESS;\n}\n\nstatic int callback_read_int(void * param, int nb_fields, char * value[], char * field[])\n{\n   int * ival = (int *) param;\n   if (sscanf(value[0], \"%d\", ival) != 1)\n       return -1;\n   return 0;\n}\n\nstatic int callback_capteur(void * unused, int nb_fields, char * value[], char * field[])\n{\n   int i;\n   printf(\"    -&gt; \");\n   for (i = 0; i &lt; nb_fields; i ++) {\n       if (strcmp (field[i], \"type\") == 0)\n           printf(\" Type : %s, \", value[i]);\n       if (strcmp(field[i], \"identifiant\") == 0)\n           printf(\" Identifiant : %s, \", value[i]);\n   }\n   printf(\"\\n\");\n   return 0;\n}<\/pre>\n<p style=\"text-align: justify;\">Il y a plusieurs mani\u00e8res d\u2019utiliser l\u2019API SQLite depuis le langage C&nbsp;; j\u2019ai choisi d\u2019employer des <em>callbacks<\/em> qui seront invoqu\u00e9es pour chaque enregistrement trouv\u00e9 lors d\u2019une requ\u00eate <code>SELECT<\/code>.<\/p>\n<p style=\"text-align: justify;\">Il faut compiler notre code avec l\u2019option <code>-lsqlite3<\/code> pour le lier avec la bonne biblioth\u00e8que. Voyons son utilisation ainsi que les principaux messages d\u2019erreur.<\/p>\n<pre>$ <strong>cc affiche-contenu-site.c -o affiche-contenu-site -lsqlite3<\/strong>\n$ <strong>.\/affiche-contenu-site<\/strong>\nusage: .\/affiche-contenu-site   \n$ <strong>.\/affiche-contenu-site domotique.sql \"Marco\" \"Reserve\"<\/strong>\n.\/affiche-contenu-site: Site Marco 'non trouve'\n$ <strong>.\/affiche-contenu-site domotique.sql \"Restaurant le Marco Polo\" \"Reserve\"<\/strong>\nRestaurant le Marco Polo\n.\/affiche-contenu-site: Piece Reserve 'non trouvee'\n$ <strong>.\/affiche-contenu-site domotique.sql \"Restaurant le Marco Polo\" \"Cuisine\"<\/strong>\nRestaurant le Marco Polo\n Cuisine\n    -&gt;  Type : PT100,  Identifiant : 100,\n    -&gt;  Type : KZ08,  Identifiant : 101,\n$<\/pre>\n<p style=\"text-align: justify;\">Bien s\u00fbr cet exemple est tr\u00e8s simple. Pour une v\u00e9ritable application embarqu\u00e9e, on a plut\u00f4t coutume d\u2019\u00e9crire un petit module qui va rechercher tous les \u00e9l\u00e9ments de configuration dans la base de donn\u00e9es et les stocker dans une structure en m\u00e9moire afin de ne plus avoir d\u2019acc\u00e8s \u00e0 r\u00e9aliser par la suite.<\/p>\n<p style=\"text-align: justify;\">On voit bien l\u2019int\u00e9r\u00eat de l\u2019emploi d\u2019une base SQLite3 pour des syst\u00e8mes o\u00f9 une partie de la configuration doit \u00eatre r\u00e9alis\u00e9e par des scripts shell (lors d\u2019une premi\u00e8re mise en service ou lors d\u2019une restauration des \u201c<em>factory presets<\/em>\u201d) ou par une interface HTML depuis le r\u00e9seau (en invoquant des scripts PHP ou TCL. L\u2019application principale, \u00e9crite en C\/C++, Java, Python, Ruby, pourra facilement lire et sauvegarder la configuration gr\u00e2ce aux modules \u00e9crits pour tous ces langages.<\/p>\n<p style=\"text-align: justify;\">Notons que le fichier repr\u00e9sentant la base de donn\u00e9es est constitu\u00e9 d\u2019une structure interne binaire pas forc\u00e9ment portable entre syst\u00e8mes diff\u00e9rents. Il est possible de demander \u00e0 sqlite3 de nous afficher une repr\u00e9sentation de la base de donn\u00e9es sur sa sortie standard (commande <code>.dump<\/code>) sous une forme textuelle qui pourra \u00eatre r\u00e9inject\u00e9e sur une autre machine (commande <code>.restore<\/code>).<\/p>\n<pre>$ <strong>sqlite3  domotique.sql  .dump<\/strong>\nPRAGMA foreign_keys=OFF;\nBEGIN TRANSACTION;\nCREATE TABLE site (\n       nom     CHAR(64),\n       adresse CHAR(512),\n       ref     INTEGER PRIMARY KEY\n   );\nINSERT INTO \"site\" VALUES('Restaurant le Marco Polo','4, avenue d''Italie - 75013 Paris',1);\nCREATE TABLE piece (\n       nom         CHAR(256),\n       volume   FLOAT,\n       ref_site INTEGER,\n       ref      INTEGER PRIMARY KEY,\n       FOREIGN KEY (ref_site) REFERENCES site(ref)\n   );\nINSERT INTO \"piece\" VALUES('Cuisine',92.4,1,1);\nINSERT INTO \"piece\" VALUES('Salle',148.3,1,2);\nCREATE TABLE capteur (\n       type        CHAR(32),\n       identifiant CHAR(32),\n       ref_piece   INTEGER,\n       FOREIGN KEY(ref_piece) REFERENCES piece(ref)\n       PRIMARY KEY(ref_piece, identifiant)\n   );\nINSERT INTO \"capteur\" VALUES('PT100','100',1);\nINSERT INTO \"capteur\" VALUES('KZ08','101',1);\nCREATE TABLE actionneur (\n       type        CHAR(32),\n       identifiant CHAR(32),\n       ref_piece   INTEGER,\n       FOREIGN KEY(ref_piece) REFERENCES piece(ref)\n       PRIMARY KEY(ref_piece, identifiant)\n   );\nCOMMIT;\n$<\/pre>\n<h1>Conclusion<\/h1>\n<p style=\"text-align: justify;\">Nous avons parcouru au fil de ces deux articles plusieurs m\u00e9thodes de chargement et sauvegarde de param\u00e8tres depuis une application en privil\u00e9giant l\u2019optique \u201csyst\u00e8me embarqu\u00e9\u201d. La plupart de ces \u00e9l\u00e9ments peuvent s\u2019appliquer pour des applications fonctionnant dans des environnements plus classiques (postes de travail, serveurs, etc.).<\/p>\n<p style=\"text-align: justify;\">Nos premiers essais avec une sauvegarde binaire \u201cbrute\u201d ont rapidement montr\u00e9 leurs limites surtout pour la p\u00e9rennit\u00e9 et la portabilit\u00e9 des donn\u00e9es, la sauvegarde des champs sous forme de textes Ascii (\u00e9ventuellement avec des sections \u00e0 la mani\u00e8re des <code>.ini<\/code> Windows) est plus robuste mais ne permet pas de repr\u00e9senter des informations avec une structure un peu complexe.<\/p>\n<p style=\"text-align: justify;\">L\u2019utilisation d\u2019une biblioth\u00e8que comme <em>libconfig<\/em> permet une repr\u00e9sentation arborescente plus pouss\u00e9e, tout comme le format XML qui dispose de nombreux outils d\u2019analyse, examen, v\u00e9rification de coh\u00e9rence, etc. Enfin l\u2019emploi d\u2019une base de donn\u00e9es offre des possibilit\u00e9s de recherche plus riches encore et une facilit\u00e9 d\u2019int\u00e9gration dans un environnement de configuration HTML au d\u00e9triment de la lisibilit\u00e9 directe du fichier par un \u00eatre humain.<\/p>","protected":false},"excerpt":{"rendered":"<p>Cet article a &eacute;t&eacute; &eacute;crit en collaboration avec mon ami Fran&ccedil;ois Beaulier (dont je vous recommande le blog). Nous avons r&eacute;fl&eacute;chi ensemble sur les possibilit&eacute;s qui s&rsquo;offrent au d&eacute;veloppeur d&rsquo;application embarqu&eacute;e pour enregistrer le param&eacute;trage, la configuration, les pr&eacute;f&eacute;rences&hellip; bref tout ce qui constitue les donn&eacute;es persistantes de son syst&egrave;me.<\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5,8],"tags":[],"class_list":["post-3550","post","type-post","status-publish","format-standard","hentry","category-embarque","category-linux-2"],"_links":{"self":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/3550","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/comments?post=3550"}],"version-history":[{"count":12,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/3550\/revisions"}],"predecessor-version":[{"id":3564,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/3550\/revisions\/3564"}],"wp:attachment":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/media?parent=3550"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/categories?post=3550"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/tags?post=3550"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}