Site Web Jquery Mobile

Le but est de créer un site WEB Jquery Mobile (Cf http://jquerymobile.com/)  hébergé sur mon Raspberry Pi et capable de communiquer avec un processus C++ au travers d’une passerelle PHP.

Pour la passerelle PHP rien de plus simple car on souhaite juste lancer un binaire sans attendre de code de retour. On utilise simplement la commande “exec” :

<?php
    $aCommandToExecute = '/home/pi/USB_Leonardo/test 1 2 "' . $_REQUEST["name"] . '"';
    echo exec($aCommandToExecute);
?>

Cette passerelle PHP sera appelée depuis les pages JavaScript du framework Jquery Mobile.

L’utilisation du framework Jquery mobile se fait simplement incluant les libs dans le header du site :

    <head>
        <meta charset="utf-8" />
        <title>Xbee handler</title>
        <link rel="stylesheet" href="http://code.jquery.com/mobile/1.0a4.1/jquery.mobile-1.0a4.1.min.css" />
        <script src="http://code.jquery.com/jquery-1.5.2.min.js"></script>
        <script src="http://code.jquery.com/mobile/1.0a4.1/jquery.mobile-1.0a4.1.min.js"></script>    
    </head>

Ensuite on référence qq pages web externes :

<body>
    <div data-role="page">
        <div data-role="header">
            <h1>Colloc</h1>
        </div>
        <div data-role="content">    
            <a href="charles.php" data-role="button" rel="external">Charles</a>  
            <a href="jeff.php" data-role="button">Jeff</a> 
            <a href="salon.php" data-role="button" rel="external">Salon</a>     
        </div>
    </div>
    </body>

ce qui donne le résultat suivant :

Ensuite on peut créer les pages correspondantes qui appelleront la passerelle pour envoyer un message au binaire C++. Un exemple de page simplifiée (un seul bouton /action) utilisant la passerelle avec qq boutons pour effectuer les actions :

<!DOCTYPE html>
 <html>
 <head>
 <meta charset="utf-8" />
 <title>Xbee handler</title>
 <link rel="stylesheet" href="http://code.jquery.com/mobile/1.0a4.1/jquery.mobile-1.0a4.1.min.css" />
 <script src="http://code.jquery.com/jquery-1.5.2.min.js"></script>
 <script src="http://code.jquery.com/mobile/1.0a4.1/jquery.mobile-1.0a4.1.min.js"></script>
 </head>
 <body>
 <script type="text/javascript">
 $(function()
 {
 $("#Bopen").click(function()
 {
 var theName = '7';
 $.ajax(
 {
 type: "POST",
 url: "http://82.227.228.35/XbeeWrapper.php",
 data: ({name: theName}),
 cache: false,
 dataType: "text",
 success: onSuccess
 });
 });
function onSuccess(data)
 {
 }
});
</script>
 <div data-role="page">
 <div data-role="header">
 <h1>Charles</h1>
 </div>
 <div data-role="content">
 <input id="Bopen" type="button" name="open" value="Ouvrir volet"/>
 </div>
 </div>
 </body>
 </html>

ce qui donne le résultat suivant :

Ce site utilise ensuite l’Arduino Leonardo pour recevoir les commandes sur le port USB et les transférer a une passerelle CPL (voir : Arduino Leonardo : USB Device)

Merge !

Il est temps de mettre tous les projets en commun :

pour obtenir un système domotique :

Le système repose sur un site web “Domos” hébergé sur un raspberry pi qui communique a divers équipement de la maison par différents média (ZigBee, Ethernet, X10, …) au travers une passerelle Arduino.

Systeme domotique x10 zigbee xbee arduino

L’ensemble des équipements peut être commande depuis le site web. Ce dernier est construit autour du framework JqueryMobile pour un affichage optimal sur les smartphone (le site web doit être accessible depuis n’importe ou pour pouvoir visualiser l’état du système a tout moment.

Le site comprend plusieurs section (correspondant aux différentes pièces de l’appartement et quelque “actions” disponible sur la page d’accueil.

 

 

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 😉

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 😉

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é.

 

Site web dynamique AJAX (Jquery et Php)

Description :

Notre but est de créer un site web pour montrer le réseau de transport indien (Aérien et Ferroviaire). L’utilisateur choisit les réseaux (uniquement 2 réseaux pour le moment : AIR et RAIL) qu’il souhaite voir et une carte est mise a jour automatiquement.

 Librairies :

  • Jquery : librairies de fonction JavaScript
    «jQuery is a fast and concise JavaScript Library that simplifies HTML document traversing, event handling, animating, and Ajax interactions for rapid web development.»
    http://jquery.com/
  • Gmap3 : librairie Jquery wrappant l’API Javascript de google map
    «A JQuery plugin to create google maps with advanced features (overlays, clusters, callbacks, events…) »
    http://gmap3.net/

 Fonctionnement :

Dans un premier temps il faut installer les frameworks nécessaires. Gmap3 contient déjà une version de Jquery et il n’est donc pas nécessaire d’installer Jquery directement. L’installation de Gmap3 est très simple (voir leur site) et se résume à 2 fichiers à uploader sur votre serveur (gmap3.min.js et jquery-1.6.1.min.js)

Il faut également une database Sqlite qui contient les gares et les aéroports indiens…. Je vous laisse trouver ca sur le net.

Le processus se fait dans 2 fichiers distincts :

ServiceProvider

Il est le provider de données et interroge la DataBase Sqlite en utilisant l’interface de PHP Data Objects (PDO). Cette interface permet une abstraction du SGBD utilise et on peut donc imaginer migrer vers une nouvelle base sans difficulté). Pour plus de détails sur PDO http://www.php.net/manual/fr/intro.pdo.php

Le Service Provider est appelé depuis la page d’accueil du site en AJAX avec un argument « action » (dont la valeur peut être AIR ou RAIL) et renvoi les stations correspondantes (aéroport ou gare) a la page d’accueil. La réponse est le résultat de la requête SQL encode en JSON (pour plus d’info sur le JSON http://fr.wikipedia.org/wiki/JavaScript_Object_Notation).

<?php
try
{
	//connect to SQLite database
	$dbh = new PDO("sqlite:../DataBase/DataBase2.db");

	if(isset($_REQUEST['action']))
	{
		switch($_REQUEST['action'])
		{
			case "AIR" :
			//SQL works
			$aSqlRequest = "select ... from AIR where (COUNTRY='IN')";
			break;
			case "RAIL" :
			$aSqlRequest = "select ... from RAIL where (COUNTRY='IN')";
			break;
		}
		$aListOfPointRequest = $dbh->query($aSqlRequest);
		$aListOfPoint = $aListOfPointRequest->fetchAll();
		//Response object
		$resultat = $aListOfPoint;
		print(json_encode($resultat));
	}

	//close the database connection
	$dbh = null;
}
catch(PDOException $e)
{
	echo $e->getMessage();
}
?>

La page d’accueil

Elle gère la vue et les interactions avec l’utilisateur.
A chaque changement dans la liste des modes de transport (RAIL ou AIR) la page envoi une requête AJAX au service provider pour récupérer les points et mettre à jour la carte. Voici le code simplifié avec les commentaires ajoute pour mieux comprendre :

$('input', $dpts).change(function () {
<<-- Recupere le tag clique (AIR ou RAIL)
  var region = $('label[for=' + $(this).attr('id') + ']', $dpts).html(),
<<-- Est ce que le tag a ete selectionne ou deselectionne
    checked = $(this).is(':checked'),
    map = $('#test1').gmap3('get'),
    markers;
<<-- Si le tag clique a ete selectionne
  if (checked) {
<<-- Appel AJAX au data provider avec le TAG clique (AIR ou RAIL)
    $.getJSON("ServiceProvider.php", {"action": region,"nombre_a": region},
<<-- fonction declanche lors de la reception AJAX du service provider
	function (data2) {
      $("input#resultat_addition").val(data2['resultat_addition']);
      var aTravelLocations = data2;
      var data = [];
<<--Mise a jour des donnees avec les nouvelles locations recu du data provider
      $.each(aTravelLocations, function (i, ville) {
        data.push({lat: ville.LATITUDE,lng: ville.LONGITUDE,tag: region,data: ville,
options: {icon: new google.maps.MarkerImage("http://djynet.net/Icon/" + region + ".png")}
        });
      });
<<-- mettre a jour la carte avec les nouveaux marqueurs (dans data)
      $('#test1').gmap3({action: 'addMarkers',markers: data});
    });
  }
<<-- le tag clique a ete deselctionne (on doit efface les marqueurs qui ont ce tag)
  else {
<<-- effacer les marqueurs qui ont le tag deselectione (AIR par exemple)
  var clear = {action: 'clear',name: 'marker',tag: region};$('#test1').gmap3(clear);
  }
});

Voilà le code complet de la page :

<html>

  <head>
	<TITLE> Indian Transportation Map </TITLE>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <script type="text/javascript" src="jquery-1.6.1.min.js"></script>
    <script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>
    <script type="text/javascript" src="gmap3.min.js"></script>
    <style>
      body {
        text-align:center;
      }
      #container {
        width: 100%;
        height: 90%;
        margin: 2% auto;
      }
      .gmap3 {
        border: 1px dashed #C0C0C0;
        width: 90%;
        height: 100%;
      }
      #dpts {
        float:right;
        width: 10%;
        height: 33%;
        text-align:left;
        border: 1px solid #999999;
        font-family: verdana;
        font-size: 11px;
        background-color:#99B3CC;
        color:#000000;
        text-shadow: #ffffff 1px 1px, #ffffff -1px 1px, #ffffff -1px -1px, #ffffff 1px -1px;
        line-height:17px;
      }
      #dpts input[type=checkbox] {
        margin-right:10px;
      }
    </style>
    <script type="text/javascript">
      $(function () {
        var regions = [];
        var k = [];
        var $dpts = $("#dpts");
        regions.push("RAIL");
        regions.push("AIR");
        regions = regions.sort();
        for (k in regions) {
          $dpts.append('<input id="chk' + k + '" type="checkbox"><label for="chk' + k + '">' + regions[k] + '</label><br />');
        }
        $('input', $dpts).change(function () {
          var region = $('label[for=' + $(this).attr('id') + ']', $dpts).html(),
            checked = $(this).is(':checked'),
            map = $('#test1').gmap3('get'),
            markers;
          if (checked) {
            $.getJSON("ServiceProvider.php", {
              "action": region,
              "nombre_a": region
            }, function (data2) {
              $("input#resultat_addition").val(data2['resultat_addition']);
              var aTravelLocations = data2;
              var data = [];
              $.each(aTravelLocations, function (i, ville) {
                data.push({
                  lat: ville.LATITUDE,
                  lng: ville.LONGITUDE,
                  tag: region,
                  data: ville,
                  options: {
                    icon: new google.maps.MarkerImage("http://djynet.net/Icon/" + region + ".png")
                  }
                });
              });
              $('#test1').gmap3({
                action: 'addMarkers',
                markers: data
              });
            });
          }
          else {
            var clear = {
              action: 'clear',
              name: 'marker',
              tag: region
            };
            $('#test1').gmap3(clear);
          }
        });
        $('#test1').gmap3({
          action: 'init',
          options: {
            center: [46.578498, 2.457275],
            zoom: 5
          }
        });
      });
    </script>

    <body>
      <div id="container">
        <div id="dpts"></div>
        <div id="test1"></div>
      </div>
    </body>

</html>

Le résultat final est visible ICI :

http://djynet.net/GoogleMapTest2/