Linux Udev pour setter les permissions sur les peripheriques USB

Suite à mon article précédant ou mon Raspberry Pi communique avec une Arduino Leonardo grâce à la lib libusb… j’ai voulu lancer mon programme avec un utilisateur non root et j’obtiens l’erreur suivante :

libusb couldn't open USB device /dev/bus/usb/002/005: Permission denied

Après quelques recherches sur Internet :

  • http://ubuntuforums.org/showthread.php?t=1939231
  • https://groups.google.com/forum/#!msg/ultra-cheap-sdr/bNn5naF4nwg/u_FjO7xC3rcJ
  • https://wiki.archlinux.org/index.php/Map_Custom_Device_Entries_with_udev

Je me rends compte que le problème vient de Udev qui crée le périphérique lors du branchement avec un accès en écriture uniquement pour le root. Pour pallier ce problème une solution simple et de créer une règle UDEV pour changer la création du périphérique à la volée. Il existe déjà beaucoup de documentation sur UDEV et je n’ai pas l’intention de faire du copier-coller 😉

Voilà la règle ajoute dans mon cas pour changer les permissions du périphérique Arduino Leonardo :

pi@raspberrypi /var/www $ sudo cat /etc/udev/rules.d/10-arduinoLeo.rules
SUBSYSTEMS=="usb", ACTION=="add", ATTRS{idVendor}=="2341", ATTRS{idProduct}=="8036", MODE:="0777"

Fini les problèmes 😉

Bonus : Protéger le site web par mot de passe (serveur lighttpd) :
http://www.cyberciti.biz/tips/lighttpd-setup-a-password-protected-directory-directories.html

Arduino Leonardo : USB Device

Il est aujourd’hui possible d’émuler un périphérique USB HID avec les carte Arduino UNO grâce a au framework LUFA. Néanmoins cela nécessite de flasher la carte et donc un programmateur externe et cela conduit également a la perte du bootloader Arduino.
Des informations sur le framework LUFA ICI et un exemple d’utilisation avec une Arduino Uno ICI

La nouvelle carte Arduino Leonardo possède également une gestion USB HID. Celle-ci est intégrée dans l’IDE. Nous allons l’utiliser pour communique avec serveur Raspberry Pi fonctionnant sous Raspbian. Cela permet de créer des périphériques HID sans devoir flasher le firmeware comme c’est le cas pour la librairie LUFA et d’utilise l’IDE de DEV Arduino.

Un thread intéressant à propos de LUFA et l’Arduino Leonardo :

It’s disappointing to see Arduino move away from LUFA in only one generation of hardware
revisions, but I understand that they need to more tightly integrate the USB code with their custom platform to make it more useful to the masses, and portable to the new expanded range of Arduino architectures.

On peut donc penser qu’il y aura de plus en plus de projet USB grâce à la nouvelle génération de carte Arduino Leonardo. Il existe déjà quelque exemple dans l’IDE 1.01 (dans le menu Fichier -> Exemple -> Leonardo) pour la gestion d’un clavier et d’une souris.

Je vais utiliser un des exemple “clavier” et la lib libusb pour essayer de communiquer entre mon raspberry pi et mon arduino Leonardo. Voilà le code Leonardo qui se contente de faire clignoter une LED s’il reçoit qq chose sur le port USB :

/* 
 Blink LED when receive something from USB
 */

//Define the pin number
const int _OutPinLedTest = 7;
const int PIN_BOUTON_ON = 4;
const int PIN_BOUTON_OFF = 3;

int _ledState=0;

void setup() {
  //defined input button
  pinMode(_OutPinLedTest,OUTPUT);
  pinMode(PIN_BOUTON_ON,INPUT);
  pinMode(PIN_BOUTON_OFF,INPUT);
  //Active resistor as pull up
  digitalWrite(PIN_BOUTON_ON,HIGH);
  digitalWrite(PIN_BOUTON_OFF,HIGH);
  // open the serial port:
  Serial.begin(9600);
  // initialize control over the keyboard:
  Keyboard.begin();
}

void flashPin(int pin, int times, int wait) 
{
  for (int i = 0; i < times; i++) {
    digitalWrite(pin, HIGH);
    delay(wait);
    digitalWrite(pin, LOW);

    if (i + 1 < times) {
      delay(wait);
    }
  }
}

void switchLedStatus(int pin) 
{
  if(_ledState == 0) {
    digitalWrite(pin, HIGH);
    _ledState = 1;
    delay(100);
  }else{
    digitalWrite(pin, LOW);
    _ledState = 0;
    delay(100);

  }
}

void loop() {
  // check for incoming serial data:
  if (Serial.available() > 0) {
    // read incoming serial data:
    char inChar = Serial.read();
    // Type the next ASCII value from what you received:
    flashPin(_OutPinLedTest, 10, 600);
    //Keyboard.write(inChar+1);
  }  
  //Read button status
  int aInputDigitalValue = digitalRead(PIN_BOUTON_ON);
  //Reset the counter if button is press
  if (aInputDigitalValue == LOW)
  {
    flashPin(_OutPinLedTest, 10, 600);
  }
  aInputDigitalValue = digitalRead(PIN_BOUTON_OFF);
  //Reset the counter if button is press
  if (aInputDigitalValue == LOW)
  {
    switchLedStatus(_OutPinLedTest);
  }
}

Cote Raspberry Pi avant de connecter la carte :

pi@raspberrypi ~ $ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 0424:9512 Standard Microsystems Corp.
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp.
Bus 001 Device 007: ID 05e3:0608 Genesys Logic, Inc. USB-2.0 4-Port HUB
Bus 001 Device 008: ID 04f2:1013 Chicony Electronics Co., Ltd

Après connexion avec la Carte Leonardo (sur un HUB USB externe) :

pi@raspberrypi ~ $ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 0424:9512 Standard Microsystems Corp.
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp.
Bus 001 Device 007: ID 05e3:0608 Genesys Logic, Inc. USB-2.0 4-Port HUB
Bus 001 Device 008: ID 04f2:1013 Chicony Electronics Co., Ltd
Bus 001 Device 010: ID 2341:8036 Arduino SA

La carte Leonardo est détecte ! Voyons voir dans le détail :

Bus 001 Device 010: ID 2341:8036 Arduino SA
Couldn't open device, some information will be missing
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0
  bDeviceProtocol         0
  bMaxPacketSize0        64
  idVendor           0x2341 Arduino SA
  idProduct          0x8036
  bcdDevice            1.00
  iManufacturer           1
  iProduct                2
  iSerial                 0
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength          100
    bNumInterfaces          3
    bConfigurationValue     1
    iConfiguration          0
    bmAttributes         0x80
      (Bus Powered)
    MaxPower              500mA
    Interface Association:
      bLength                 8
      bDescriptorType        11
      bFirstInterface         0
      bInterfaceCount         2
      bFunctionClass          2 Communications
      bFunctionSubClass       2 Abstract (modem)
      bFunctionProtocol       1 AT-commands (v.25ter)
      iFunction               0
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         2 Communications
      bInterfaceSubClass      2 Abstract (modem)
      bInterfaceProtocol      0 None
      iInterface              0
      CDC Header:
        bcdCDC               1.10
      CDC Call Management:
        bmCapabilities       0x01
          call management
        bDataInterface          1
      CDC ACM:
        bmCapabilities       0x06
          sends break
          line coding and serial state
      CDC Union:
        bMasterInterface        0
        bSlaveInterface         1
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0010  1x 16 bytes
        bInterval              64
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        1
      bAlternateSetting       0
      bNumEndpoints           2
      bInterfaceClass        10 CDC Data
      bInterfaceSubClass      0 Unused
      bInterfaceProtocol      0
      iInterface              0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x02  EP 2 OUT
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x83  EP 3 IN
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               0
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        2
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      0 No Subclass
      bInterfaceProtocol      0 None
      iInterface              0
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.01
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 Report
          wDescriptorLength     101
         Report Descriptors:
           ** UNAVAILABLE **
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x84  EP 4 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               1

Pour mieux comprendre à quoi correspondent ces informations vous pouvez jeter à la présentation de la norme USB disponible ICI. Le schéma suivant permet de voir les différents “composants” d’un périphérique USB :

Et si on adapte a l’Arduino Leonardo :

Si ma mémoire est bonne (la dernière fois que j’ai utilisé la lib libusb été à l’époque où j’utilisai les microcontroleur PIC avec une communication Linux USB – PIC 18f4550) il nous faut ces 2 informations :

idVendor           0x2341 Arduino SA
idProduct          0x8036

Cote Raspbian

J’ai trouvé un vieux code C++ que j’utilisai pour communiquer avec les PIC 18f4550 qui fonctionne avec la lib libusb. On doit donc commencer par l’installer :

pi@raspberrypi ~/USB_Leonardo $ sudo apt-get install libusb-1.0-0-dev

Ensuite….un petit tour sur la doc de la lib disponible ICI pour vérifier les dernières mise a jour/info. Ensuite j’ai mis à jour mon vieux programme (ca n’a pas bcp change en deux…) pour que l’utilisateur puisse changer d’interface et de configuration sans devoir tout recompiler. Voilà le programme :

#include <errno.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
//C++ migration
#include <iostream>

#include <stdlib.h>
#include <libusb-1.0/libusb.h>

#define VERSION "0.1.0"

#define VENDOR_ID 9025
//CWA
#define LINUX
#define PRODUCT_ID 32822

// HID Class-Specific Requests values. See section 7.2 of the HID specifications
#define HID_GET_REPORT                0x01
#define HID_GET_IDLE                  0x02
#define HID_GET_PROTOCOL              0x03
#define HID_SET_REPORT                0x09
#define HID_SET_IDLE                  0x0A
#define HID_SET_PROTOCOL              0x0B
#define HID_REPORT_TYPE_INPUT         0x01
#define HID_REPORT_TYPE_OUTPUT        0x02
#define HID_REPORT_TYPE_FEATURE       0x03

#define CTRL_IN                LIBUSB_ENDPOINT_IN|LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE
#define CTRL_OUT       LIBUSB_ENDPOINT_OUT|LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE

const static int PACKET_CTRL_LEN=2; 

const static int PACKET_INT_LEN=2;
const static int INTERFACE=2;
const static int ENDPOINT_INT_IN=0x81; /* endpoint 0x81 address for IN */
const static int ENDPOINT_INT_OUT=2; /* endpoint 1 address for OUT */
const static int TIMEOUT=5000; /* timeout in ms */

void bad(const char *why)
{
    fprintf(stderr,"Fatal error> %s\n",why);
    exit(17);
}

static struct libusb_device_handle *devh = NULL;

static int find_lvr_hidusb(void)
{
    devh = libusb_open_device_with_vid_pid(NULL, VENDOR_ID, PRODUCT_ID);
    return devh ? 0 : -EIO;
}

int main(void)
{
    //Store the interface number to use
    int aInterface;
    std::cout << "Please enter an interface ID (integer value): ";
    std::cin >> aInterface;
    std::cout << "The interface used will be : " << aInterface <<std::endl;

    //Store the configuration number to use
    int aConfiguration;
    std::cout << "Please enter an configuration ID (integer value): ";
    std::cin >> aConfiguration;
    std::cout << "The configuration used will be : " << aConfiguration <<std::endl;

    int r = 1;
    unsigned char *data = new unsigned char[1]; //data to write
    data[0]=0x00; //some dummy values
    //for (int compt=0; compt<63;compt++)
    //data[compt]=0x00; //some dummy values
    int actual; //used to find out how many bytes were written
    int actual2; //used to find out how many bytes were written

    r = libusb_init(NULL);
    if (r < 0) {
        fprintf(stderr, "Failed to initialise libusb\n");
        exit(1);
    }
    printf("Successfully initialise libusb\n");

    libusb_set_debug(NULL,4);

    r = find_lvr_hidusb();
    if (r < 0) {
        fprintf(stderr, "Could not find/open LVR Generic HID device\n");
        goto out;
    }
    printf("Successfully find the LVR Generic HID device\n");

#ifdef LINUX
    printf("LINUX - we need to see if a kernel driver is active on the interface.\nIf yes we have to detach it to be able to claim the interface\n");

    r = libusb_kernel_driver_active(devh, aInterface);

    if (r==0)
    {
        printf("No Kernel Driver active on this interface\n");
    }
    else
    {
        printf("Kernel Driver already active on this interface - we have to detach it to be able to claim the interface\n");
        r = libusb_detach_kernel_driver(devh, aInterface);  
        if (r < 0) 
        {
            fprintf(stderr, "libusb_detach_kernel_driver error %d\n", r);
            goto out;
        }
        printf("Kernel Driver detach\n");
    }

#endif

    /*r =  libusb_reset_device(devh);
    if (r < 0) {
        fprintf(stderr, "libusb_set_configuration error %d\n", r);
        goto out;
    }
    printf("Successfully reset usb \n");*/

    r = libusb_set_configuration(devh, aConfiguration);
    if (r < 0) {
        fprintf(stderr, "libusb_set_configuration error %d\n", r);
        goto out;
    }
    printf("Successfully set usb configuration 1\n");

    r = libusb_claim_interface(devh, aInterface);
    if (r < 0) {
        fprintf(stderr, "libusb_claim_interface error %d\n", r);
        goto out;
    }
    printf("Successfully claimed interface\n");
    r = libusb_bulk_transfer(devh, ENDPOINT_INT_OUT, data, 1, &actual2, 0);  
    if(r == 0 && actual2 == 1) //we wrote the 4 bytes successfully 
    {
        fprintf(stderr, "Successfully perform bulk transfer \n");
    }
    else
    {
    fprintf(stderr, "libusb_bulk_transfer error %d\n", r);
        goto out;
    }

    //r = libusb_bulk_transfer(devh, ENDPOINT_INT_OUT, data, 64, &actual, 0); 
    r = libusb_interrupt_transfer(devh, ENDPOINT_INT_OUT, data, 1, &actual, 0); 
    if(r == 0 && actual == 1) //we wrote the 4 bytes successfully 
    {
        fprintf(stderr, "Successfully perform interupt transfer \n");
    }
    else
    {
    fprintf(stderr, "libusb_interrupt_transfer error %d\n", r);
        goto out;
    }

    libusb_release_interface(devh, 0);
out:
    //     libusb_reset_device(devh);
    libusb_close(devh);
    libusb_exit(NULL);
    return r >= 0 ? r : -r;

}

Voilà ce que ça donne au final :

pi@raspberrypi ~/USB_Leonardo $ sudo ./test
Please enter an interface ID (integer value): 1
The interface used will be : 1
Please enter an configuration ID (integer value): 1
The configuration used will be : 1
Successfully initialise libusb
Successfully find the LVR Generic HID device
LINUX - we need to see if a kernel driver is active on the interface.
If yes we have to detach it to be able to claim the interface
No Kernel Driver active on this interface
Successfully set usb configuration 1
Successfully claimed interface
Successfully perform bulk transfer
[timestamp] [threadID] facility level [function call] <message>
--------------------------------------------------------------------------------
[ 0.000000] [000007ca] libusbx: error [submit_bulk_transfer] submiturb failed error -1 errno=22
libusb_interrupt_transfer error -1

et une led qui clignote 😉

Migration Arduino Uno vers Arduino Leonardo

Cet article traite de la migration de l’application CPL décrite dans un article précédant “Application domotique avec Arduino et CPL ” accessible ici vers une plateforme Arduino Leonardo.

La carte Arduino Leonardo (présentation ICI) est la première carte Arduino à utiliser une “vrai” liaison USB :
The Leonardo differs from all preceding boards in that the ATmega32u4 has built-in USB communication, eliminating the need for a secondary processor. This allows the Leonardo to appear to a connected computer as a mouse and keyboard, in addition to a virtual (CDC) serial / COM port. It also has other implications for the behavior of the board; these are detailed on the getting started page.

Un des premiers changements est de changer la liaison série par défaut de l Arduino Uno vers la liaison série non USB de l’Arduino Leonardo (car la liaison série par défaut de l’Arduino Uno est l’USB). La librairie Xbee doit donc être modifie (merci a http://arduino.cc/forum/index.php/topic,111354.0.html) en remplaçant la ligne:

_serial = &Serial;

par :

#if defined(USBCON)
   _serial = &Serial1;
#else
   _serial = &Serial;
#endif

Il faut aussi modifier la création de l’objet X10 pour ne pas utiliser une interruption “custom” car elles ne fonctionnent pas sur l’Arduino Leonardo voir http://vort.org/tag/bugs/ et http://code.google.com/p/arduino/issues/detail?id=714

On va donc utilise une interruption standard sur le pin 2 grâce au code suivant :

X10ex x10ex = X10ex(
  1, // Zero Cross Interrupt Number (2 = "Custom" Pin Change Interrupt)
  2, // Zero Cross Interrupt Pin (Pin 4-7 can be used with interrupt 2)
  9, // Power Line Transmit Pin 
  10, // Power Line Receive Pin
  true, // Enable this to see echo of what is transmitted on the power line
  powerLineEvent, // Event triggered when power line message is received
  1, // Number of phases (1 = No Phase Repeat/Coupling)
  50 // The power line AC frequency (e.g. 50Hz in Europe, 60Hz in North America)
);

au lieu de

X10ex x10ex = X10ex(
  2, // Zero Cross Interrupt Number (2 = "Custom" Pin Change Interrupt)
  4, // Zero Cross Interrupt Pin (Pin 4-7 can be used with interrupt 2)
  5, // Power Line Transmit Pin 
  6, // Power Line Receive Pin
  true, // Enable this to see echo of what is transmitted on the power line
  powerLineEvent, // Event triggered when power line message is received
  1, // Number of phases (1 = No Phase Repeat/Coupling)
  50 // The power line AC frequency (e.g. 50Hz in Europe, 60Hz in North America)
);

Après ça… tout fonctionne 😉

J’upload ici le REPO complet fonctionnel : ED2

Raspberry Pi : installation de Raspbian

Contexte

Mon Raspberry Pi est enfin arrivé ce week-end :

Grosso modo il s’agit d’un mini PC sur une architecture ARM avec un port Ethernet…pour 40E ! Idéal pour un petit serveur web communicant avec mes montages Arduino 😉 Plus d’info sur le Raspberry Pi sur la page officiel ICI.

Hardware

Tout d’abord le matériel… il suffit de 3 choses :

  • Câble alimentation micro usb capable de fournir plus de 700 mA (la majorité des chargeurs de portable feront l’affaire MAIS pas un port USB de PC dont le courant est limite (200mA si ma mémoire est bonne)),
  • Cable ethernet,
  • Carte SD de 4Go et classe 4 au minimum

Le clavier, la souris, et la sortie HDMI sont inutile puisque j’utilise putty pour me connecter sur le Pi (même la première fois).

Software

Installation

J’ai choisi d’installer une distribution spécialement dédié au Pi (base sur Debian) : Raspbian. Il s’agit d’une des distributions conseillées sur le site officiel dont l’image et la présentation sont disponible ICI.

La création de la carte SD est assez simple et clairement détaillé ICI.

Une fois la carte terminée est mise en place… le Raspberry peut effectuer son premier boot. Le Pi va obtenir en adresse IP grâce au serveur DHPC présent sur notre réseau. Cette adresse nous servira pour pouvoir se logger sur le Pi en SSH avec Putty. Il est maintenant possible de se connecter sur le Pi en utilisant putty (attendre qq minutes après la mise sous tension pour laisser le temps au boot de se terminer).

Le login par défaut est “user:pi // pass:raspberry).

Bonus :
Dans mon cas je souhaite que mon Raspberry garde toujours la même adresse IP donc je vais modifier la table d’adressage DHCP de mon serveur pour que l’adresse MAC de mon Pi soit toujours associe à l’IP 192.168.0.6.Pour cela il faut l’adresse MAC que l’on obtient avec ifconfig :

pi@raspberrypi ~ $ ifconfig
eth0      Link encap:Ethernet  HWaddr b8:27:eb:df:0e:2e
          inet addr:192.168.0.9  Bcast:192.168.0.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:158 errors:0 dropped:0 overruns:0 frame:0
          TX packets:132 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:13571 (13.2 KiB)  TX bytes:15732 (15.3 KiB)

Puis il suffit d’ajouter cette adresse dans le serveur DHCP avec lP 192.168.0.6

 Mise à jour

La version de Raspbian disponible au moment où j’écris cet article présente certains bugs en particulier pour le module lighttpd (dont on aura besoin pour le serveur WEB) qui empêche l’installation. On va donc mettre a jour la version avec APT:

pi@raspberrypi ~ $ sudo apt-get update
...
pi@raspberrypi ~ $ sudo apt-get upgrade
...

Installation du serveur Web

Rien de plus simple….a condition d’avoir mis a jour sa version !

pi@raspberrypi ~ $ sudo apt-get instal lighttpd

et si tout se passe bien vous devriez être capable d’ouvrir une page web sur l’IP de votre Pi qui devrait ressemble à :

Il faut ensuite activer la gestion du php en commençant par :

pi@raspberrypi /var/www $ sudo apt-get install php5-cgi

il suffit ensuite d’activer le support des modules ds lighttpd avec :

pi@raspberrypi /var/www $ sudo lighttpd-enable-mod fastcgi
Enabling fastcgi: ok
Run /etc/init.d/lighttpd force-reload to enable changes
pi@raspberrypi /var/www $ sudo lighttpd-enable-mod fastcgi-php
Enabling fastcgi-php: ok
Run /etc/init.d/lighttpd force-reload to enable changes

On peut tester le résultat avec la page d’exemple suivante :

pi@raspberrypi /var/www $ cat test.php
<html>
<head>
<title>PHP Test</title>
</head>
<body>
<?php phpinfo(); ?>
</body>
</html>

 Installation du serveur FTP

Pour le serveur FTP j’ai choisi vsftpd pour sa légèreté et rapidité. L’installation se déroule sans pb avec APT :

pi@raspberrypi ~ $ sudo apt-get install vsftpd
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following NEW packages will be installed:
  vsftpd
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 149 kB of archives.
After this operation, 329 kB of additional disk space will be used.
Get:1 http://mirrordirector.raspbian.org/raspbian/ wheezy/main vsftpd armhf 2.3.5-3 [149 kB]
Fetched 149 kB in 5s (27.8 kB/s)
Preconfiguring packages ...
Selecting previously unselected package vsftpd.
(Reading database ... 54581 files and directories currently installed.)
Unpacking vsftpd (from .../vsftpd_2.3.5-3_armhf.deb) ...
Processing triggers for man-db ...
Setting up vsftpd (2.3.5-3) ...
Starting FTP server: vsftpd.

Apres l’installation il n’est pas encore possible de se connecter sur le Pi. On obtient pour le moment l’erreur suivante (avec le user pi) :

Statut :    Connexion à 192.168.0.9:21...
Statut :    Connexion établie, attente du message d'accueil...
Réponse :    220 (vsFTPd 2.3.5)
Commande :    USER pi
Réponse :    530 This FTP server is anonymous only.
Erreur :    Impossible d'établir une connexion au serveur

Alors que la connexion fonctionne bien pour les utilisateurs anonymes. On va donc changer la configuration du daemon pour interdire les connections anonyme et autoriser les utilisateurs a se logger:

pi@raspberrypi ~ $ grep local_enable /etc/vsftpd.conf
#local_enable=YES
devient 
pi@raspberrypi ~ $ grep local_enable /etc/vsftpd.conf
local_enable=YES

et

pi@raspberrypi ~ $ grep anonymous_en /etc/vsftpd.conf
anonymous_enable=YES
devient
pi@raspberrypi ~ $ grep anonymous_en /etc/vsftpd.conf
anonymous_enable=NO

la connexion anonyme est maintenant interdite et il est possible de se logger avec l’utilisateur Pi:

L’installation est terminée 😉

Application domotique avec Arduino et CPL

Le but de ce projet et de pouvoir commander certains équipements depuis un site web hébergé sur un raspberry pi (je l’attends toujours…) qui communique avec un arduino relié à une passerelle CPL. Les ordres sont transmis aux différents équipements au travers du CPL en utilisant le protocole X10.

Je vous conseille vivement de lire le document de Patrick Abati qui présente le protocole X10 (le meilleur que j’ai pu trouver pour comprendre le fonctionnement) disponible ICI ou x10 presentation.

Les différents modules présents sur le réseau électrique sont :

J’utilise une librairie déjà existante qui peut être téléchargé ici pour dialoguer avec la passerelle X10 de marmitek : https://code.google.com/p/arduino-x10/

La communication se fait également avec une liaison Xbee avec une carte mère (la même que cette utilise dans l’article sur la station météo). De plus je vais ajouter une liaison USB dès que j’aurai reçu mon Arduino Leonardo (avec une vrai gestion USB et non une liaison série émulé). Cette liaison USB servira a communique avec un Raspberry Pi pour commander les équipements depuis un site Web (en plus de la carte mère).

Voilà le programme complet:

//Xbee library
#include <XBee.h>
//X10
#include <X10ex.h>

#define POWER_LINE_MSG "PL:"
#define POWER_LINE_BUFFER_ERROR "PL:_ExBuffer"
#define SERIAL_DATA_MSG "SD:"
#define SERIAL_DATA_THRESHOLD 1000
#define SERIAL_DATA_TIMEOUT "SD:_ExTimOut"
#define MODULE_STATE_MSG "MS:"
#define MSG_DATA_ERROR "_ExSyntax"
//Next

XBee _Xbee = XBee(); // create Xbee object to control a Xbee
ZBRxResponse _ZbRxResp = ZBRxResponse(); //Create reusable response objects for responses we expect to handle
int _ServoPosition = 90;

//Define the pin number
const int _OutPinLedTest = 8;

// Fields used for serial and byte message reception
unsigned long sdReceived;
char bmHouse;
byte bmUnit;
byte bmCommand;
byte bmExtCommand;
int _CmdReceived = 0;

// X10 Power Line Communication Library
X10ex x10ex = X10ex(
  2, // Zero Cross Interrupt Number (2 = "Custom" Pin Change Interrupt)
  4, // Zero Cross Interrupt Pin (Pin 4-7 can be used with interrupt 2)
  5, // Power Line Transmit Pin 
  6, // Power Line Receive Pin
  true, // Enable this to see echo of what is transmitted on the power line
  powerLineEvent, // Event triggered when power line message is received
  1, // Number of phases (1 = No Phase Repeat/Coupling)
  50 // The power line AC frequency (e.g. 50Hz in Europe, 60Hz in North America)
);

void setup()
{
  //defined input button
  pinMode(_OutPinLedTest,OUTPUT);

  // start serial
  _Xbee.begin(9600);
    // Remember to set baud rate in Serial Monitor or lower this to 9600 (default value)
  Serial.begin(9600);
  // Start the Power Line Communication library
  x10ex.begin();
}

void flashPin(int pin, int times, int wait) 
{
  for (int i = 0; i < times; i++) {
    digitalWrite(pin, HIGH);
    delay(wait);
    digitalWrite(pin, LOW);

    if (i + 1 < times) {
      delay(wait);
    }
  }
}

void loop()
{
  _Xbee.readPacket();
  //Reset command to 0
  _CmdReceived = 0;

  if (_Xbee.getResponse().isAvailable()) {
    // got something
    Serial.println("We have something on the serial");
    Serial.print("ApiId: 0x");
    Serial.println(_Xbee.getResponse().getApiId(), HEX);

    if (_Xbee.getResponse().getApiId() == ZB_RX_RESPONSE) {
      Serial.println("This is a ZB response");

      // now fill our zb rx class
      _Xbee.getResponse().getZBRxResponse(_ZbRxResp);
      flashPin(_OutPinLedTest, 3, 200);
      _CmdReceived = _ZbRxResp.getData(0);

    }  
  } 
  //Process the command
  //Test if we have an action to do 
  if(_CmdReceived==5)
  {
    x10ex.sendCmd('A', 4, CMD_ON, 1);
  }
  else if(_CmdReceived==6)
  {
    x10ex.sendCmd('A', 4, CMD_OFF, 1);
  }
  else if(_CmdReceived==7)
  {
    x10ex.sendCmd('A', 5, CMD_ON, 1);
  }
  else if(_CmdReceived==8)
  {
    x10ex.sendCmd('A', 5, CMD_OFF, 1);
  }
  else if(_CmdReceived==9)
  {
    x10ex.sendCmd('A', 7, CMD_ON, 1);
  }
  else if(_CmdReceived==10)
  {
    x10ex.sendCmd('A', 7, CMD_OFF, 1);
  }
  else if(_CmdReceived==11)
  {
    x10ex.sendCmd('A', 7, CMD_ON, 1);
    x10ex.sendCmd('A', 5, CMD_ON, 1);
  }
  else if(_CmdReceived==12)
  {
    x10ex.sendCmd('A', 7, CMD_OFF, 1);
    x10ex.sendCmd('A', 5, CMD_OFF, 1);
  }
}

void printX10Message(const char type[], char house, byte unit, byte command, byte extData, byte extCommand, int remainingBits)
{
  printX10TypeHouseUnit(type, house, unit, command);
  // Ignore non X10 commands like the CMD_ADDRESS command used by the IR library
  if(command <= 0xF)
  {
    Serial.print(command, HEX);
    if(extCommand || (extData && (command == CMD_STATUS_ON || command == CMD_STATUS_OFF)))
    {
      printX10ByteAsHex(extCommand);
      printX10ByteAsHex(extCommand == EXC_PRE_SET_DIM ? extData & B111111 : extData);
    }
  }
  else
  {
    Serial.print("_");
  }
  Serial.println();
}

void printX10TypeHouseUnit(const char type[], char house, byte unit, byte command)
{
  Serial.print(type);
  Serial.print(house);
  if(
    unit &&
    unit != DATA_UNKNOWN/* &&
    command != CMD_ALL_UNITS_OFF &&
    command != CMD_ALL_LIGHTS_ON &&
    command != CMD_ALL_LIGHTS_OFF &&
    command != CMD_HAIL_REQUEST*/)
  {
    Serial.print(unit - 1, HEX);
  }
  else
  {
    Serial.print("_");
  }
}

void printX10ByteAsHex(byte data)
{
  Serial.print("x");
  if(data <= 0xF) { Serial.print("0"); }
  Serial.print(data, HEX);
}

byte charHexToDecimal(byte input)
{
  // 0123456789  =>  0-15
  if(input >= 0x30 && input <= 0x39) input -= 0x30;
  // ABCDEF  =>  10-15
  else if(input >= 0x41 && input <= 0x46) input -= 0x37;
  // Return converted byte
  return input;
}

// Process messages received from X10 modules over the power line
void powerLineEvent(char house, byte unit, byte command, byte extData, byte extCommand, byte remainingBits)
{
  printX10Message(POWER_LINE_MSG, house, unit, command, extData, extCommand, remainingBits);
}

 

Station météo Zigbee

Présentation

Le but de ce projet est la réalisation d’une station capable d’effectuer certaines mesures (luminosité, température, humidité, humidité du sol) et d’envoyer ces données à une autre carte par ZigBee.

Voilà la carte de mesure :

composée de :

  1. Capteur de mesure d’humidité du sol de DF robot (http://www.dfrobot.com/index.php?route=product/product&product_id=599)
  2. Carte Arduino Fio + Module Xbee Serie 2 (en mode API/ZigBee)
  3. Capteur de température et humidité RHT03 de MaxDetect (http://www.sparkfun.com/products/10167)
  4. Capteur de luminosité Phidgets 1127 (http://www.phidgets.com/products.php?product_id=1127_0) déjà utilise lors de précédents montage (lien vers article)
  5. Chargeur/batterie solaire pour téléphone portable relie à l’Arduino Fio

La carte de mesure communique avec une seconde carte autonome (je prévois de la relie a un Raspberry pi (http://www.raspberrypi.org/) dès que j’aurai réussi à m’en procurer un 😉

Voici la carte autonome :

composée de :

  1.  Arduino Uno + “communication shield” avec un module Xbee Serie 2 en Mode Zigbee Routeur API
  2. Carte BP + Led pour interagir avec l’utilisateur
  3. Afficheur LCD I2C Barton déjà utilise dans des montages précédents

La carte Fille

Au boot la carte fille initialise certains éléments tels que la liaison série, Xbee dans la fonction d’init.

void setup()
 {
 // start serial port
 Serial.begin(9600);
 // start serial
 _Xbee.begin(9600);
 }

Ensuite le programme entre dans la boucle principale qui vérifie si un message est arrivé sur la liaison ZigBee

//Read if we received an inoming message
 _Xbee.readPacket();
 if (_Xbee.getResponse().isAvailable()) {
 // got something

Si un message est disponible alors il est stocke dans “_CmdReceived” (qui devient donc non nulle)

_CmdReceived = _ZbRxResp.getData(0);

Ensuite le programme va effectuer certains relevés (température, humidité, luminosité,…) en fonction de la valeur reçu :

if(_CmdReceived==1)
 {
 _DataToSend=analogRead(_InPinLedMeasure);
 }
 else if((_CmdReceived==2)||(_CmdReceived==3))
 {
 delay(50);
 DHT22_ERROR_t errorCode;
 Serial.print("Requesting data...");
 errorCode = _Dht22.readData();
 switch(errorCode)
 {
 case DHT_ERROR_NONE:
 if (_CmdReceived==2)
 {
 _DataToSend=_Dht22.getTemperatureCAsInt();
 }
 else if(_CmdReceived==3)
 {
 _DataToSend=_Dht22.getHumidityAsInt();
 }
 ...
 break;
 }
 }
 else if(_CmdReceived==4)
 {
 _DataToSend=analogRead(_InPinMoistureMeasure);
 }

Enfin, le process envoi la valeur mesure a la carte mère par ZigBee

aPayload[0] = _DataToSend & 0xff; //LSB
 Serial.print("Data0: 0x");
 Serial.println(aPayload[0], HEX);
 aPayload[1] = (_DataToSend >> 8) & 0xff; //MSB
 Serial.print("Data1: 0x");
 Serial.println(aPayload[1], HEX);
// Specify the address of the remote XBee (this is the SH + SL)
 XBeeAddress64 aAddr64 = XBeeAddress64(0x0013a200, 0x400a3e5e);
// Create a TX Request
 ZBTxRequest aZbTx = ZBTxRequest(aAddr64, aPayload, sizeof(aPayload));
// Send your request
 _Xbee.send(aZbTx);

Remarque importante sur la carte fille :

Pour la communication série avec le capteur de température DHT22 j’utilise une librairie disponible ICI. La communication ZigBee entre les modules Xbee se fait en Mode API (pour pouvoir utiliser un réseau MESH) avec la librairie disponible ICI. Je remercie d’ailleurs les DEV de ses librairies qui m’ont fait gagner un temps fou !

La carte Mère

La carte mère est composée d’un adruino UNO + shield ZigBee/Xbee et de 2 interfaces utilisateur : un écran LCD Barton (communication série) et une carte avec 5 BP et 5Leds.

L’écran LCD Barton est le même que dans mes montages précédant ou vous trouverez plus d’explication dessus et des exemples de codes. Les BP sont utilisés par l’utilisateur pour :

  • Choisir la commande a envoyer ({“Moisture”, “Temp”, “Humidity”, “Light”};)
  • Choisir le destinataire de la commande ({0x406b7b64, 0x400a3e5d};). Il s’agit en fait de l’adresse Xbee du module que l’on target.
  • Envoyer la commande au destinataire.

Le choix de la commande et le choix du destinataire fonctionne de la même façon. Les différentes possibilités sont mise dans un ENUM et lie a deux autres tableau : un tableau pour le texte et un autre tableau pour le code de la commande.

enum Tcommand {
  Moisture=0,
  Temp,
  Humidity,
  Light,
  LastCommandIndex
};
const int _possibleCommand[] = {4, 2, 3, 1};
const String _possibleCommandTxt[] = {"Moisture", "Temp", "Humidity", "Light"};

Lors d’un appuie sur le bouton correspondant l’enum est incrémenté (et remis a zéro si on arrive au dernier) et le texte correspondant est affiche sur le LCD grâce a cette méthode :

void changeCommand()
{
  _Command=(Tcommand)(_Command+1);
  if (_Command>=LastCommandIndex)
  {
    _Command=(Tcommand)0;
  }
  lcd.clear();
  lcd.printstr("Command :");
  lcd.setCursor(1,0);
  lcd.printstr(_possibleCommandTxt[_Command]);
}

Une fois que la commande et le destinataire sont choisis il suffit d’envoyer le message avec un appuie sur un BP.

int aPayload=_possibleCommand[_Command];
    Serial.print("We are going to send a ZigBee message with Cmd : ");
    Serial.print(_Command);
    Serial.print(" which correspond to value : ");
    Serial.print(aPayload);
    Serial.print(" to this destination : ");
    Serial.print(_Receiver);
    Serial.print(" which correspond to value : ");
    Serial.println(_possibleReceiver[_Receiver]);
    sendZigBeeMsg(aPayload,_possibleReceiver[_Receiver]);

La commande est envoyée par ZigBee. Vous trouverez qq infos sur le ZigBee (pour les modules Xbee) dans mes articles précédents. Je vous rappelle que j’utilise une très bonne lib Xbee dispo ICI. Par rapport a mes premiers montage/code Zigbee j’ai ajouté la gestion du ACK qui fera sonner un buzzer si le message n’est pas bien envoyé/reçu.

void sendZigBeeMsg(unsigned int iPayLoad, unsigned long iAddrToTarget)
{
  Serial.println("We are going to send a ZigBee message");
  // Create an array for holding the data you want to send.
  uint8_t aPayload[1];
  // Fill it with the data
  aPayload[0] = iPayLoad;

  // Specify the address of the remote XBee (this is the SH + SL)
  XBeeAddress64 addr64 = XBeeAddress64(0x0013a200, iAddrToTarget);

  // Create a TX Request
  ZBTxRequest zbTx = ZBTxRequest(addr64, aPayload, sizeof(aPayload));

  // Send your request
  _Xbee.send(zbTx);
  Serial.println("Message Sent - Waiting for the ACK");

  if (_Xbee.readPacket(5000)) {
    Serial.println("We got a response to the message");

    // should be a znet tx status  
    ZBTxStatusResponse aZbTxStatus = ZBTxStatusResponse();        
    if (_Xbee.getResponse().getApiId() == ZB_TX_STATUS_RESPONSE) {
      _Xbee.getResponse().getZBTxStatusResponse(aZbTxStatus);

      // get the delivery status, the fifth byte
      if (aZbTxStatus.getDeliveryStatus() == SUCCESS) {
        Serial.println("The Trx was OK");
      } 
      else {
        Serial.println("Warning : The Trx was KO");
      }
    } 
    else{
      Serial.print("It was not a Trx status. ApiId:");
      Serial.println(_Xbee.getResponse().getApiId());
    }   
  } 
  else {
    Serial.println("Warning : This should never happen");
    flashPin(_OutPinBuz1, 1, 250);
  }
}

La carte mère vérifie également si une donnée a été reçue d’une carte fille (une réponse à une commande envoyée) et l’affiche sur le LCD.

if (_Xbee.getResponse().isAvailable()) {
    // got something
    Serial.println("We have something on the serial");
    Serial.print("ApiId: 0x");
    Serial.println(_Xbee.getResponse().getApiId(), HEX);

    if (_Xbee.getResponse().getApiId() == ZB_RX_RESPONSE) {
      Serial.println("This is a ZB response");

      // now fill our zb rx class
      _Xbee.getResponse().getZBRxResponse(_ZbRxResp);

      Serial.print("Data0: 0x");
      Serial.println(_ZbRxResp.getData(0), HEX);
      Serial.print("Data1: 0x");
      Serial.println(_ZbRxResp.getData(1), HEX);
      _ServoPosition=word(_ZbRxResp.getData(1),_ZbRxResp.getData(0));
      //_ServoPosition = int(_ZbRxResp.getData(0),_ZbRxResp.getData(1));
      updateDisplayedValue(_ServoPosition);

      if (_ZbRxResp.getOption() == ZB_PACKET_ACKNOWLEDGED) {
        Serial.println("This is a ZB ACK");
      } 
    }  
  }

La prochaine étape est d’ajouter la possibilité de piloter la carte mère grâce à un site WEB PHP et une liaison USB depuis mon serveur Linux (ou un Raspberry Pi) pour envoyer les commandes. Je prévois également d’ajouter un pilotage CPL pour commander l’ouverture de volets.

 

Open street map et Jquery

Depuis que google map est devenu payant de nombreux site cherche des alternatives tel que Apple (Apple remplace Google Maps par OpenStreetMap…) et fourSquare (FourSquare drops Google Maps API for OpenStreetMap…). Il semble que Open Street Map (OSM) soit une alternative crédible et j’ai donc décidé de l’essayer.

“OpenStreetMapest un projet international fondé en 2004 dans le but de créer une carte libre du monde…” plus d’info sur le site officiel ICI.

Pour test OSM j’ai utilisé un vieux projet (affichage des gares indiennes) dont l’article original est disponible ICI et une API jquery pour OSM : jQueryGeo.

“jQuery Geo is an open-source, geospatial mapping jQuery plugin from Applied Geographics developed with the intention of making spatial web mapping significantly simpler than it may initially seem. We would like to point out the term “open-source” to explictly state that Google, Bing, MapQuest, Yahoo! & Esri are generally free but not open….” plus de détails sur le site officiel ICI.

Le site a 2 buts :

  • il donne des informations sur une location contenue dans ma base de “point de voyage”. Pour cela j’utilise un Jquery autocomplete field avec comme source de donne une fonction qui fait un appel JSON a un page web. Cette page web est écrite en PHP et interroge une base SQlite pour trouver toutes les locations qui match le texte utilisateur.
  • Le second but est de convertir n’importe quelle adresse en un point géographique grâce à l’API google geocode de Google. Ensuite on désire afficher les 30 “point de voyage” les plus proches de l’adresse rentrée par l’utilisateur. Pour cela on calcul une pseudo distance entre les coordonnées de l’adresse et tous les points de notre base de données.

Voilà à quoi ressemble le résultat final que vous pouvez l’essayer ICI.

Les points clés :

La communication AJAX entre l’input field Jquery et son feeder (le service provider) est déjà décrite dans un précédant article disponible ICI et je ne la détail pas une seconde fois.

La communication avec l’API google de geocoding est assez simple. La documentation officielle est disponible ICI mais pour faire simple et rapide…. Il suffit d’inclure la “librairie”

<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        ...
        <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
        ...
    </head>
    <body>
    ...

et ensuite on peut utiliser la fonction geocode du geocoder de Google pour feeder notre autocomplete input field (comme pour n’importe quelle fonction classique, Voir la doc sur le site de Jquery ou mon article ICI pour plus de détails sur ce type d’input field)

$('input#address').autocomplete({
    source: function (request, response) {
        geocoder.geocode({
            'address': $('input#address').val()
        }, function (results, status) {
            response($.map(results, function (item) {
                console.log(item);
                return {
                    label: item.formatted_address,
                    value: item
                }
            }));
        });
    },
    ...

Le calcul de la distance entre les coordonnées géographiques (Latitude et Longitude) qui correspondent à l’adresse de l’utilisateur et l’ensemble des “points de voyage” de notre DataBase n’est pas juste. Il s’agit d’une pseudo distance pour être plus rapide dans le temps de réponse.

max(LATITUDE," . $aInput2 . ")-min(LATITUDE," . $aInput2 . ")+max(LONGITUDE," . $aInput3 . ")-min(LONGITUDE," . $aInput3 . ") as distance

L’utilisation de JqueryGeo est très simple. Cette API offre moins de service que celle de Google (les Cluster n’existent pas ce qui est gênant quand on veut afficher beaucoup de points) mais le service répond rapidement ce qui important pour le ressenti utilisateur. Il suffit d’inclure la lib (comme pour le service google).

<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        ...
        <script src="//code.jquerygeo.com/jquery.geo-1.0a4.min.js" type="text/javascript"></script>
        ...
    </head>
    <body>
    ...

et ensuite la carte peut être créée avec

$("#map").geomap({
    center: [1, 43],
    zoom: 6});

que l’on va remplir avec les points de voyage en utilisant la méthode suivante

$("#map").geomap("append", {
    type: "Point",
        coordinates: [value.LONGITUDE, value.LATITUDE]
        }, {
            color: "red"
        });

Il existe une bonne documentation sur le site officiel ICI. Le code de mon exemple est un peu différent car je suis en train de changer le type de la carte pour permettre à l’utilisateur de cliquer sur la carte et d’avoir des informations sur les points sélectionnés.

$("#map").geomap({
    center: [1, 43],
    zoom: 6,
    mode: "find",
    cursors: { find: "crosshair" },
    click: function (e, geo){
      var outputHtml = "",
          result = $("#map").geomap("find", geo, 8);

      $.each(result, function () {
        outputHtml += ("Found a " + this.type + " at " + this.coordinates);
      });

      //$(".output ul").html(outputHtml);
      console.log(outputHtml);
    }
});

Je ferais une update de l’article quand j’aurai terminé.

 

Fritzing – Creation de CI

Fritzing est un logiciel open source qui permet de créer des circuits imprimés assez facilement. Avec un peu de chance tous les composants que vous utilisez seront déjà présents dans la bibliothèque (assez réduite à l’heure actuelle). Le résultat est plutôt limite pour l’instant car la gestion du multi-couche n’est pas implémenté. Un exemple de résultat :

Plus de détails sur leur page officiel ici.

Pour créer un nouveau composant je vous conseille de commencer par lire la doc officiel ici ET surtout de ne rien faire avant d’avoir tout compris (il est aussi très pratique d’utiliser les templates disponibles ici).

Voici le résultat obtenu pour le capteur de lumière phidget 1127 :

Source ZIP : Phidget1127_Fritzing

Update library LCDI2C vers arduino 1.0

Ce week-end j’ai voulu utiliser mon afficheur LCD I2C Batron BT HQ 21605AV sur mon nouveau projet ZigBee (un exemple de projet utilisant cet afficheur LCD ici). Comme il s’agit d’un projet que j’ai commencé récemment j’utilise la version 1.0 d’Arduino et la librairie n’est plus compatible.

Cette librairie a été développée en 2009 par Digid (https://public.me.com/digid/fr/) mais pas maintenue à jour par manque de temps. J’ai donc décidé de la migrer à Arduino V1.0 et je poste ici la nouvelle Version en espérant qu’elle puisse servir à d’autres personnes. Les modifications ne sont pas compliquées et détaillées dans le header.

La version compatible Arduino 1.0 est donc disponible ici -> LCD