Archives par étiquette : embarqué

GPIO – Servos moteurs, joypad et WiFi

IMG_5494

Nous allons voir dans cet article comment réaliser une station de pilotage à distance pour commander des servos moteurs, à l’aide d’un Raspberry Pi, d’un joypad et d’un lien WiFi. Dans cet exemple j’uitiliserai un eeePC 901 pour la station de pilotage, mais il est tout à fait possible d’utiliser un deuxième Raspberry Pi.

Voici un schéma global :

pi joystick servoLa construction de ce dispositif se fait en trois étapes :

  1. Contrôle des servos moteurs par le Pi
  2. Utilisation du joypad
  3. Envoi des commandes à distance par wifi

Liste du matériel utilisé dans cet article

 

1 – Contrôle des servos moteurs par le Pi

Pour piloter plusieurs servos moteurs avec un Pi, nous n’avons d’autre choix que de passer par une carte contrôleur. Le site Adafruit propose justement un circuit permettant de contrôler jusqu’à 16 servos moteurs en utilisant le bus I²C (disponible sur le GPIO du Pi comme expliqué dans cet article GPIO – Entrée en matière).

imageVous pouvez acheter ce circuit directement sur le site d’Adafruit : Adafruit 16-Channel 12-bit PWM/Servo Driver – I2C interface – PCA9685

imageVous devrez sortir votre fer et souder les broches sur le circuit imprimé 🙂 Un bornier est prévu pour connecter une source d’alimentation. J’utilise un bloc coupleur de pile avec 4 piles AA pour fournir une tension de 6V, parfait pour alimenter mes servos moteurs.

Côté servos, je dispose de deux gros FUTABA S3010 et d’un minuscule FUTABA S3114.

Le branchement de la carte sur le Pi peut se faire simplement à l’aide de wire jumpers. Il suffit de connecter l’alimentation (3,3V et la masse/ground) ainsi que les broches SCL et SDA comme sur le schéma ci-dessous :

branchement_16cUne fois les branchements effectués, il convient d’activer le support de l’I²C sur le Raspberry Pi. Ceci se fait en deux étapes :

1 – Editez le fichier /etc/modules et ajoutez les deux lignes suivantes :

i2c-bcm2708
i2c-dev

2 – Editez ensuite le fichier /etc/modprobe.d/raspi-blacklist.conf et commentez les deux lignes suivantes (en ajoutant # au début de ces deux lignes) :

# blacklist spi-bcm2708
# blacklist i2c-bcm2708

Pour finir, installez l’outil i2c-tools et redémarrez le Pi :

apt-get install i2c-tools
shutdown -r now

Pour vérifer que la carte est bien reconnue par le Pi, lancez la commande suivante :

i2cdetect -y 1 # pour la révision 2 du Pi

ou

i2cdetect -y 0 # pour la première version du Pi

Vous devriez obtenir ce résultat :

root@raspberrypi:~# i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: 40 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: 70 -- -- -- -- -- -- --

Bien ! Nous allons maintenant écrire le programme en Python qui va nous permettre de piloter ces servos moteurs. Python est installé par défaut sur Raspbian, il ne manque que certains paquets :

apt-get install python-dev python-rpi.gpio python-smbus

Adafruit propose une librairie Python qui permet de dialoguer facilement avec les différents composants distribués par son site. Nous n’allons pas nous en priver 🙂 Pour télécharger la dernière version disponible de cette librairie, il suffit d’en récupérer les sources sur le dépôt GIT :

git clone https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code.git

La partie qui nous intéresse ici se trouve dans le répertoire Adafruit-Raspberry-Pi-Python-Code/Adafruit_PWM_Servo_Driver.

Dans un premier temps, voici un petit script qui permet de tester les servos test_servo.py :

#!/usr/bin/python

from Adafruit_PWM_Servo_Driver import PWM
import sys

pwm = PWM(0x40, debug=True)
pwm.setPWMFreq(60)

pwm.setPWM(int(sys.argv[1]), 0, int(sys.argv[2]))

Pensez à rendre ce script exécutable grâce à la commande chmod 755 test_servo.py

Ce script prend deux paramètres :

  • l’id du port contrôlant un servo de 0 à 15
  • une valeur, généralement entre 150 et 600 qui détermine la position que doit prendre le servo

Exemples pour commander le servo branché sur le premier port (ayant pour id 0) :

./test_servo.py 0 480
./test_servo.py 0 240

Et voilà, si tout s’est bien passé, vous devriez pouvoir piloter vos servos de cette manière. Nous verrons plus loin dans cet article comment les piloter avec un joypad et à distance 🙂

Note : Je ne sais absolument pas pourquoi le troisième paramètre de la fonction setPWM() doit être entre 150 et 600… De même, je ne sais pas à quoi correspond le deuxième paramètre attendu par cette fonction. Si quelqu’un connaît la réponse, merci de nous expliquer cela en commentaire de cet article 🙂

2 – Utilisation du joypad

26-127-505-02Je dispose d’un magnifique joypad USB, modèle Saitek P990 munis de deux mini-joysticks ou « chapeaux chinois ». Nous allons utiliser ces deux joysticks pour piloter nos servos.

Un joystick qu’est ce que c’est ? En fait, c’est simplement un potentiomètre dont la valeur (la résistance) est interprétée et traduite en nombre. Sur ce modèle de joypad, les valeurs sont échantillonnées de 0 à 1023 (soit 1024 valeurs possibles). Pour obtenir deux axes, il y a deux potentiomètres par joystick. Avec deux joysticks, nous avons donc de quoi piloter indépendamment 4 servos 🙂

La librairie Python evdev permet d’interagir facilement avec les événements déclenchés par des périphériques USB. Pour l’installer :

apt-get install python-pip python-dev
pip install evdev

Avant d’écrire un script Python qui sache interpréter les actions de ces joysticks, vous devez connaître l’identifiant « event » attribué par le système au moment où vous branchez votre joypad USB.

lsusb
Bus 001 Device 003: ID 05e3:0608 Genesys Logic, Inc. USB-2.0 4-Port HUB
Bus 001 Device 005: ID 05e3:0505 Genesys Logic, Inc.
Bus 002 Device 003: ID 06a3:040b Saitek PLC P990 Dual Analog Pad
Bus 005 Device 002: ID 0b05:b700 ASUSTek Computer, Inc. Broadcom Bluetooth 2.1
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 004 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 005 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 001 Device 006: ID 11b0:6148 ATECH FLASH TECHNOLOGY
Bus 001 Device 007: ID 05af:3062 Jing-Mold Enterprise Co., Ltd Cordless Keyboard
Bus 001 Device 008: ID 046d:c016 Logitech, Inc. Optical Wheel Mouse

Mon joypad est bien reconnu par le système avec la dénomination : « Saitek PLC P990 Dual Analog Pad« .

cat /proc/bus/input/devices

Cherchez la référence à votre joypad et repérez l’identifiant « event », ici nous avons l’identifiant « event6« .

[...]

I: Bus=0003 Vendor=06a3 Product=040b Version=0100
N: Name="Saitek P990 Dual Analog Pad"
P: Phys=usb-0000:00:1d.0-2/input0
S: Sysfs=/devices/pci0000:00/0000:00:1d.0/usb2/2-2/2-2:1.0/input/input15
U: Uniq=
H: Handlers=event6 js0
B: PROP=0
B: EV=1b
B: KEY=3fff 0 0 0 0 0 0 0 0 0
B: ABS=30027
B: MSC=10

[...]

A chaque fois que l’on va presser un bouton sur le joypad, ou actionner un de ses joysticks, un événement sera déclenché. Il s’agit donc de surveiller en permanence les événements déclenchés et d’agir en fonction. Voici maintenant un petit script Python qui permet de tester votre joypad et d’identifier les différents événements liés à chaque joystick et chaque bouton : test_joypad.py

#!/usr/bin/python

from evdev import InputDevice, categorize, ecodes
from time import sleep
from datetime import date
import os, sys, socket

dev = InputDevice('/dev/input/event6') # reprendre le même identifiant "event"
print(dev)

for event in dev.read_loop(): # boucle qui surveille l'arrivée d'un événement
  e_code=event.code
  e_type=event.type
  e_value=event.value
  print(str(e_type)+' - '+str(e_code)+' - '+str(e_value))

A chaque fois que vous toucherez quelque chose sur votre joypad, ce script affichera trois variables, e_code, e_type et e_value :

./test_joystick.py
device /dev/input/event6, name "Saitek P990 Dual Analog Pad", phys "usb-0000:00:1d.0-2/input0"
3 - 0 - 508
3 - 0 - 412
3 - 0 - 347
3 - 0 - 391
3 - 0 - 457
3 - 0 - 511
3 - 1 - 602
3 - 1 - 701
3 - 1 - 771
3 - 1 - 659
3 - 1 - 561
3 - 1 - 511

Voilà ce que j’obtiens en actionnant le joystick gauche de mon joypad, identifié par l’e_code 3. Dans l’axes des X, j’obtiens un e_type 0, et un e_type 1 pour l’axe des Y. Lorsque je relâche le joystick en position centrale, les valeurs sont proches de 512 (valeur médiane entre 0 et 1023). Ces caractéristiques sont propres à ce modèle de joypad, mais le principe de fonctionnement est le même pour tous 🙂 Ce petit script vous permettra de noter l’e_code et l’e_type de chaque bouton et joystick que vous voulez utiliser.

Pour ma part, j’ai trois servos à piloter, et je dispose de deux joysticks avec chacun deux axes. J’utiliserai l’axe X du joystick gauche (e_code 3, e_type 0) pour piloter le servo 1. L’axe Y du joystick gauche (e_code 3, e_type 1) pour piloter le servo 2. Et enfin l’axe X joystick droit (e_code 3, e_type 5) pour piloter le servo 3 🙂

A partir de là, vous pouvez facilement imaginer associer le servo 1 au roulis d’un avion, le servo 2 au tangage et le servo 3 au lacet par exemple 🙂

La dernière étape consiste à envoyer les commandes du joypad, branché sur la station de pilotage, au Raspberry Pi qui répercutera les ordres sur ses servos 🙂

3 – Envoi des commandes à distance par wifi

Nous avons d’un côté un script Python qui tourne sur un Raspberry Pi qui a pour rôle de piloter des servos. De l’autre, nous avons un eeePC (ou autre machine…), qui exécute un second script Python dont le but est d’interpréter les commandes envoyées par un joypad. Il s’agit maintenant de faire communiquer ces deux scripts entre eux, ce que nous pouvons facilement faire grâce aux sockets 🙂

Sans trop rentrer dans les détails, il s’agit d’ouvrir une socket au niveau du script qui s’exécute sur le Pi afin qu’il soit à l’écoute de messages envoyés par le script côté « station de pilotage ». Le script côté « station de pilotage » se connecte à cette socket en passant par le réseau IP (via WiFi), et envoie les commandes déclenchées par le joypad.

Côté Raspberry Pi :

#!/usr/bin/python

from Adafruit_PWM_Servo_Driver import PWM
import sys, os, socket

listen_address = ('0.0.0.0', 12800) # écoute sur toutes les interface sur le port 12800

srv_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # protocole UDP
srv_socket.bind(listen_address)
print "server enabled"

pwm = PWM(0x40, debug=True) # initialisation de la carte contrôleur
pwm.setPWMFreq(60)

while True:
  query, clt_address = srv_socket.recvfrom(1024) # écoute les message
  order=query.split('_') # on découpe les messages reçus
  servo=order[0]
  pos=order[1]
  print str(query)+' - '+str(servo)+' - '+str(pos) # on affiche les infos
  pwm.setPWM(int(servo), 0, int(pos)) # on envoie la commande au servo
sys.exit(0)

Côté Station de pilotage :

#!/usr/bin/python

from evdev import InputDevice, categorize, ecodes
from time import sleep
from datetime import date
import os, sys, socket

now = date.today()
print(now)

clt_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # protocole UDP
srv_address = ('192.168.101.27', 12800) # on vise l'adresse IP du Pi sur le port 12800

dev = InputDevice('/dev/input/event6') # initialisation du joypad
print(dev)

def convertAxis (value, axis_max): # fonction qui convertie les valeurs des joystick
  facteur=axis_max/490
  new_value=int(round((value/facteur)+150, 0))
  return new_value

for event in dev.read_loop(): # pour chaque événement détecté
  e_code=event.code
  e_type=event.type
  e_value=event.value
  print(str(e_type)+' - '+str(e_code)+' - '+str(e_value))
  if e_type == 3:
    if e_code == 0: # à destination du servo 1
      servo_pos=str(convertAxis (e_value, 1024))
      clt_socket.sendto("0_"+servo_pos, srv_address)
    elif e_code == 1: # à destination du servo 2
      servo_pos=str(convertAxis (e_value, 1024))
      clt_socket.sendto("1_"+servo_pos, srv_address)
    elif e_code == 5: # à destination du servo 3
      servo_pos=str(convertAxis (e_value, 512))
      clt_socket.sendto("2_"+servo_pos, srv_address)

Conclusions

En déclinant ce principe, vous pouvez piloter de nombreux servos moteurs à distance et commencer à envisager la construction systèmes complexes comme des robots ou des drones 🙂

En parlant de drone, ces articles relatifs au GPIO me serviront à construire un drone avion, comme expliqué ici : Raspberry Pi BOA Drone 🙂

Cependant, certaines zones d’ombre persistes, notamment sur le pilotage de servos. L’utilisation de la lib distribuée par Adafruit évite de se poser de question, cela fonctionne immédiatement. La contre partie, c’est qu’on ne comprend pas forcément tout ce qui se passe. Aussi, j’invite les lecteurs de ce blog à intervenir en commentaire de cet article afin d’apporter des informations complémentaires, des idées ou simplement des remarques 🙂

Merci à vous 🙂

GPIO – Entrée en matière

Grâce à son port GPIO (General Purpose Input/Output), le Raspberry Pi peut interagir avec le monde réel en communiquant avec des capteurs, des moteurs, et un tas d’autres composants électroniques (sondes de température, LEDs, capteurs gyroscopiques, boussoles, GPS, servos moteurs, récepteurs IR/RF…). De la petite commande relais pour piloter vos guirlandes de Noël, au robot, en passant par la station météo et le drone, les possibilités sont sans limites, ou presque 🙂

pi_gpio

Le matériel de base

Qui dit GPIO, dit composants électroniques 🙂 Il va de soit que des compétences en électronique de base sont requises. De plus, il convient de s’équiper d’un minimum de matériel afin de pouvoir tester et réaliser de petits circuits.

imageVoici, selon moi, le stricte minimum du matériel à acheter avant de démarrer :

Le port GPIO

Le port GPIO du Pi comporte 26 broches qui peuvent être utilisées de manières différentes. Le schéma ci-dessous (publié par le site eLinux) montre le rôle de chaque broches ainsi que les protocoles qu’elles supportent.

GPIOsOn distingue 7 types de broches et usages qu’on peut en faire :

Ces différents « bus » et « protocoles » permettent d’interfacer le Pi avec d’autres cartes, modules ou composants électroniques.

Les exemples d’utilisation les plus simples des broches de type GPIO sont l’allumage et l’extinction d’une LED, l’interaction avec un bouton/interrupteur, le contrôle de relais, etc,…

L’I²C, le SPI, l’UART et le PWM permettent quant à eux de communiquer avec des microcontrôleurs pour réaliser des opérations plus complexes telles que le contrôle de servos moteurs, la lecture de mesures prises par des capteurs etc,…

Alimentation des broches

Avant toute chose il est important de préciser les broches du port GPIO sont alimentées en 3,3V. Seules les deux broches d’arrivée de courant (en rouge) fournissent une tension de 5V.

Ce qui veut dire qu’en mode ouput (sortie) la tension fournie par une broche est de 3,3V et délivrera un courant de 50mA max. Vous devrez donc vous assurer que les composants que vous reliez à ces broches supportent se type de courant. En mode input (entrée), une broche ne tolérera qu’une tension maximum de 3,3V ! Vous devrez donc toujours faire attention à vos branchements pour éviter d’endommager votre Raspberry Pi 🙂 

En outre, il est préférable d’utiliser une source d’alimentation tierce pour les composants et modules électroniques que vous souhaitez ajouter dans votre circuit. En effet, le Raspberry Pi n’est pas suffisamment robuste pour alimenter correctement et sans danger des circuits électroniques. Pour allumer une LED pas de problème, mais pour faire tourner un moteur, prévoyez une source d’alimentation dédiée.

Dans l’absolue, et pour des raisons évidentes de sécurité, je conseille d’utiliser des piles plutôt que de travailler directement sur le secteur (d’où le coupleur de pile dans la liste du matériel de base) 😉

Prendre la main sur le port GPIO

WiringPi est un outil qui permet de contrôler les différentes broches du port GPIO. Pour installer WiringPi :

git clone git://git.drogon.net/wiringPi
cd ./wiringPi
./build

A partir de là, vous pouvez obtenir l’état de toutes les broches grâce à la commande :

gpio readall

Ce qui devrait vous afficher un tableau de ce type :

readallChaque ligne du tableau représente une broche du port GPIO et vous indique sont mode IN/OUT (entrée/sortie) et sont état/valeur 0/1 (low/high). La colonne GPIO représente le numéro de la broche tel que décrit sur le schéma en haut de cet article. La colonne wiringPi représente le numéro de broche que vous devez utiliser en utilisant la commande gpio.

De manière très simplifiée, si vous voulez allumer une LED, vous devez mettre la broche en mode sortie/output. En mettant l’état de la broche à 1/up, la LED s’allumera. Si vous voulez savoir si un bouton/interrupteur est ouvert ou fermé, vous devez mettre la broche en mode entrée/input. Si vous lisez la valeur 0/down, alors l’interrupteur est ouvert. Si vous lisez la valeur 1/up, alors l’interrupteur est fermé 🙂 Dans la pratique c’est un tout petit peu plus complexe, mais il s’agit là de comprendre le principe.

Pour chaque broche, vous pouvez changer le mode entrée/sortie ou in/out grâce à la commande (<pin> étant le numéro de la broche ciblée) :

gpio mode <pin> in
gpio mode <pin> out

En mode entrée/read vous pouvez lire l’état d’une broche avec la commande :

gpio read <pin>

En mode sortie/write, vous pouvez changer l’état d’une grâce à la commande :

gpio write <pin> 0
gpio write <pin> 1

Pour plus d’information sur la commande gpio : http://wiringpi.com/the-gpio-utility/

Quelques exemples de montages simples

En cherchant un peu sur le web vous trouverez une multitude de tutos qui expliquent comment faire de petits montages simples. Inutile de plagier la terre entière en recopiant ici ces tutos, je préfère vous donner directement quelques liens :

12 – En résumé

IMG_5413Ce dashscreen est accroché au mur de mon séjour depuis presque un an maintenant, et je m’en sers tous les jours 🙂 L’affichage de l’heure et de la date en fait une horloge pratique et très lisible depuis toute la pièce. L’affichage de la météo est également une information utile au quotidien. Quant à la Terre et la Lune, je reconnais que l’utilité est moindre, mais je trouve cela vraiment chouette :). Si ce n’est pas votre truc, vous pouvez facilement remplacer ces images par des photos, pour ajouter une fonction « cadre photo numérique » intégrée à votre dashscreen 😉

Les autres modules, ping, TeamSpeak, bande passante et VPN me sont utiles pour surveiller ce qui se passe sur mon réseau et être alerté des pannes et autres problèmes qui peuvent survenir.

Juste au dessus du module graphique de la bande passante, j’ai intégré un graphique du nombre de visite quotidienne de ce blog 🙂 J’utilise GAPI pour récupérer les statistiques de Google Analytics, puis JSAPI (Google Charts) pour créer le graphique.

Pour les modules, le principe est toujours le même :

  1. une <div> avec un id dans le fichier index.php
  2. une feuille de style style.css pour positionner la div en question et gérer la mise en forme des données affichées
  3. une fonction javascript dans le fichier javascript.js qui lance une requête AJAX à intervalle régulier
  4. une ligne dans le fichier ajax.php pour appeler la bonne fonction PHP
  5. une fonction PHP dans le fichier inc.php pour générer le code HTML pour les images à afficher

Télécharger les sources

Vous pouvez télécharger toutes les sources ici : PiHomeDashScreen

Note : Vous devrez adapter le code à vos besoins.

Etendre le projet ?

Il sera vraiment intéressant de répertorier tous les modules créer sur ce principe par tous les utilisateurs qui se lance dans la réalisation d’un dashscreen. Nous pourrions mettre en place un « dépôt » pour stocker et partager tous les modules existants 🙂 Si cela intéresse du monde, n’hésitez pas à foncer 🙂

Nous pourrions également proposer une appli en ligne avec tous ces modules pour que le Pi n’est qu’à pointer sur une URL pour afficher une page web avec différentes options et modules activés/désactivés 😉

Remerciements

J’aimerai remercier toutes les personnes qui sont intervenues en commentaire ou sur forum, ou qui m’ont contacté directement par mail. Merci pour l’intérêt que vous portez à ce projet, ainsi que pour vos questions, remarques et astuces qui ont contribué à améliorer le projet. Merci également pour les erreurs qui m’ont été remontées avec beaucoup d’indulgence 😉

Le matériel

Un Pi et un écran suffisent pour réaliser ce projet. Vous pouvez choisir le type et la taille d’écran que vous souhaitez. Vous pouvez brancher votre Pi sur votre télé pour avoir un affichage en grand dans votre salon, ou utiliser un tout petit écran de 7″ à 10″ pour vous créer un réveil high-tech (l’idée m’a été soufflée sur un forum 🙂 ). Vous pouvez également étendre les fonctionnalités de ce dashscreen en ajoutant du son, ou un écran tactil pour interagir avec les informations affichées.

Technos et logiciels utilisés

  • Linux, Raspbian
  • script bash
  • Apache
  • PHP5
  • HTML & CSS
  • JavaScript
  • netcat
  • Xplanet
  • imagemagick

Sommaire

Forums de discussion

7 – Module 4 – Ping de serveur

Si vous avez des serveurs, des NAS, ou n’importe quel autre type de machine dont vous souhaitez connaitre l’état (allumé/éteint, ou disponible/indisponible), ce type de module, encore une fois très simple, vous sera d’une grande utilité 🙂

jarvis_screenshot_pingL’idée est d’afficher en vert les serveurs qui répondent correctement aux solicitations du module, et en rouge ceux qui ne répondent pas. Pour accentuer le caractère alarmant d’une machine qui ne répond pas, nous ajouterons une animation pour optenir ce résultat :

ping

index.php

Comme pour les autres modules, une seule <div> suffit.

 <div id="ping"></div>

La balise reste vide, et sera « remplie » par la fonction JavaScript.

style.css

Pour ce module, la feuille de style est à peine plus longue :

/* ping */

div#ping
{
  left              : 0px;
  bottom            : 0px;
  position          : absolute;
  overflow          : hidden;
  background-color  : rgba(0, 0, 0, 1);
}

td.ping
{
  font-size         : 30px;
  text-align        : left;
  border-radius     : 10px;
  padding           : 5px 10px;
  font-weight       : bold;
}

td.ping_up
{
  color             : #00DD00;
}

td.ping_down
{
  background-image  : url('pict/warning.gif');
  color             : #DD0000;;
}

Les noms des machines sont affichés dans un tableau <table> dont chaque case <td> peut soit utiliser le style « ping_up« , si la machine répond, soit « ping_down«  si la machine ne répond pas. Dans ce dernier cas, nous utiliserons une image, un gif animé, pour faire « clignoter » la case en rouge, du mois, en donner l’impression 🙂

javascript.js

La fonction javascript de ce module ne fait qu’une requête AJAX, et affiche le résultat dans la div de la page index.php :

/* PING */

var ping_timeout;

function ping ()
{
  $.ajax({
    async : false,
    type: "GET",
    url: "./ajax.php",
    data: "block=ping",
    success: function(html){
      $("#ping").html(html);
    }
  });

  ping_timeout = setTimeout("ping()", 30000);
}

Il est important d’avoir une vision temps réel de l’état de ses machines, nous fixons donc le délai de rafraichissement à 30 secondes. Vous pouvez réduire ce délai selon vos besoins, mais veillez à ce qu’il reste supérieur au temps que met la fonction PHP pour tester toutes vos machines. Dans mon cas, je surveille six machines dont trois à travers internet. En moyenne, il faut 4 à 5 secondes pour tester toutes les machines. Toutefois, il n’est pas rare que cela prenne 20 secondes ! J’ai donc fixer à 30 secondes le temps à attendre entre chaque rafraichissement du module.

ajax.php

Il suffit de rajouter ces quelques lignes au fichier ajax.php pour que la bonne fonction soit appelée :

/////////////////////////////////////////////////
  //  PING
  /////////////////////////////////////////////////

  if($block == 'ping'){
    echo ping();
  }

inc.php

Dans ce fichier nous allons déclarer la fonction ping() qui est chargée de lancer les requêtes sur les différents serveurs, puis de construire le tableau HTML qui sera affiché.

En général, pour surveiller l’état d’un serveur, on utilise la commande ping qui permet simplement de savoir si la machine ciblée répond aux requêtes ICMP. Cela pose plusieurs problèmes. En effet, il est fort probable que le trafic ICMP soit bloqué par un firewall en amont du serveur visé ce qui faussera le résultat. De plus, si vous souhaiter surveiller un serveur web par exemple, il se peut très bien que la machine réponde bien aux pings, mais que le service web soit indisponible. Les pings n’ont donc que peu d’intéret…

L’idée, est plutôt de verier si les services qui tournent sur nos machines sont bien disponibles. Pour cela, nous allons simplement ouvrir une socket grâce à la fonction PHP fsockopen(). Si l’ouverture de la socket échoue c’est que le service n’est pas disponible 🙂

/////////////////////////////////////////////////
  //  PING
  /////////////////////////////////////////////////

  function ping () {
    $hosts    = array();
    // Définition des machines à tester
    $hosts_ip = array(
                    'Oberon'    => array('10.0.0.1',  '22'),    // SSH
                    'Triton'    => array('10.0.0.2',  '15915'), // TeamSpeak
                    'Ananke'    => array('10.0.0.3',  '2049'),  // NFS
                    'ds110j'    => array('10.0.0.4',  '2049'),  // NFS
                    'ds210j'    => array('10.0.0.5',  '2049'),  // NFS
                    'ds212j'    => array('10.0.0.6',  '2048')   // NFS
                );

    // pour chaque machine on test l'ouverture d'une socket sur le port spécifié
    // on stock le résultat 'up' ou 'down' dans la variable $hosts
    foreach($hosts_ip as $hostname => $host_data){
      $host_ip    = $host_data[0];
      $host_port  = $host_data[1];
      $socket     = 0;
      $socket     = @fsockopen($host_ip, $host_port, $errno, $errstr, 3);
      if($socket && !$errno){$hosts[$hostname] = 'up';}
      else{$hosts[$hostname] = 'down';}
    }

    // construction du tableau HTML
    // pour chaque machine testée, on affiche son nom et on fixe le style 'ping_up' ou 'ping_down' à la balise td
    $html  = '';
    $html .= '<table cellspacing="10px">';
    $c=0;
    foreach($hosts as $hostname => $host_status){
      if($c == 0){$html .= '<tr>';}
      $html .= '<td class="ping ping_'.$host_status.'">'.$hostname.'</td>';
      $c++;
      if($c == 2){$c = 0; $html .= '</tr>';}
    }
    if($c != 0){$html .= '</tr>';}
    $html .= '</table>';

    return $html;
  }

La variable $hosts_ip contient la liste des machines à tester et afficher. Le nom à affiché, l’adresse IP, et le port TCP du service à tester y sont stocké. Vous devez adapter les noms, adresses IP et numéro de port à vos besoins.

Comme vous pouvez le voir dans les commentaires du code, selon la machine à tester, j’ouvre une socket sur des services tels que SSH, TeamSpeak et NFS.  Cela fonctionne également très bien sur des serveurs FTP, HTTP, POP3, etc,… Tant que le service à tester écoute sur un port TCP, cela fonctionnera 🙂

Note : Il est possible d’optimiser cette fonction PHP en parallèlisant l’ouverture des sockets sur chaque machine. En effet, cette fonction teste les machines une par une ce qui peut être long… En « forkant » cette tâche grâce à la fonction PHP pcntl_fork(), il est possible de lancer toutes les requêtes d’un seul coup et ainsi diminuer le temps total que met la fonction pour renvoyer le tableau HTML à afficher. N’hésitez pas à laisser un commentaire si vous souhaitez plus de détails sur cette manière de procéder 🙂

Optimisation en utilisant le fork

Suite à la demande de lepopeye en commentaire de cet article, je vais expliquer comment paralléliser l’ouverture des sockets et ainsi gagner un temps précieux à chaque exécution de la fonction.

Le principe est simple : Pour chaque machine à tester, nous allons créer un processus fils grâce à la fonction PHP fork_pcntl() qui lancera la fonction fsockopen(). Comme les processus fils et le processus père ne peuvent pas partager de variables (à moins de faire appel à des techniques de mémoire partagée…), il convient de stocker les résultats dans une petite base de données SQlite 🙂 Le processus père attend que tous les processus fils soient terminés pour aller lire le contenu de la base de données SQlite (qui contient l’état de chaque machine). Le reste est inchangé, nous construisons le tableau HTML à afficher comme dans la fonction ping() initiale 🙂 A savoir que le fork n’est possible qu’en ligne de commande, il faudra donc adapter le fichier ajax.php de cette manière :

  /////////////////////////////////////////////////
  //  PING
  /////////////////////////////////////////////////

  if($block == 'ping'){
    echo shell_exec('php5 fork.php'); // fork.php étant le fichier dans lequel se trouve la fonction ping_fork();
  }

Voici la version « forkée » de la fonction ping(), que vous pouvez utiliser directement en la copiant dans un fichier fork.php :

function ping_fork() {

  $hosts_ip = array(
                    'Oberon'    => array('10.0.0.1',  '22'),    // SSH
                    'Triton'    => array('10.0.0.2',  '15915'), // TeamSpeak
                    'Ananke'    => array('10.0.0.3',  '2049'),  // NFS
                    'ds110j'    => array('10.0.0.4',  '2049'),  // NFS
                    'ds210j'    => array('10.0.0.5',  '2049'),  // NFS
                    'ds212j'    => array('10.0.0.6',  '2048')   // NFS
                );

  $pids  = array();

  // Connexion à la base de données sqlite et création de la table hosts_status si elle n'existe pas encore
  $db = new SQLite3('ifstat/hosts.sqlite');
  $db->exec('CREATE TABLE IF NOT EXISTS hosts_status (host_name VARCHAR(10), host_status VARCHAR(5));');

  // pour chaque machine, on créé un processus fils
  foreach($hosts_ip as $host_name => $host){
    $pids[$host_name] = pcntl_fork();
    if(!$pids[$host_name]) {
      $socket = @fsockopen($host[0], $host[1], $errno, $errstr, 3);
      if($socket && !$errno){$status = 'up';}else{$status = 'down';}
      // on attend que la table hosts_status ne soit plus verrouillée par un éventuel accès concurrent
      if($db->busyTimeout(5000)){
        $db->exec("INSERT INTO hosts_status VALUES ('$host_name', '$status');");
      }
      exit();
    }
  }

  // le processus père doit attendre que tous les processus fils soient terminés
  foreach($pids as $host_name => $pid){
    pcntl_waitpid($pid, $status, WUNTRACED);
  }

  $results = $db->query('select * from hosts_status;');

  $html  = '';
  $html .= '<table cellspacing="10px">';
  $c=0;
  while($host = $results->fetchArray(SQLITE3_ASSOC)){
    if($c == 0){$html .= '<tr>';}
    $html .= '<td class="ping ping_'.$host['host_status'].'">'.$host['host_name'].'</td>';
    $c++;
    if($c == 2){$c = 0; $html .= '</tr>';}
  }
  if($c != 0){$html .= '</tr>';}
  $html .= '</table>';

  $db->exec("DELETE FROM hosts_status;");

  return $html;
}

echo ping_fork();

Le timeout de la fonction fsockopen() est ici fixé à 3 secondes, ce qui veut dire que l’on doit attendre au moins 3 secondes avant de déclarer la machine cible comme « down » (non disponible). Dans la version non forkée de la fonction ping() si une machine ne répondait pas, il fallait attendre 3 secondes avant de tester la machine suivante, et ainsi de suite. Avec mes 6 machines, si aucune ne répond, il aurait fallu attendre 6×3 = 18 secondes avant d’avoir le résultat final. Avec la version forkée, les 6 requêtes sont envoyées en même temps. Quelque soit le nombre de machines à tester, nous aurons donc une réponse au bout de 3 secondes maximum 🙂

J’utilise à présent cette version forkée sur mon dashscreen 🙂 Merci à lepopeye de m’avoir forcé un peu la main 😉

10 – Un boitier en Légos

J’aime beaucoup les Légos, les possibilités sont sans limites, et c’est très pratique pour réaliser de petits boitiers fonctionnels et sur mesures 🙂 En attendant que les imprimantes 3D deviennent abordables, je n’ai pas trouvé mieux 🙂

image

Comme vous pouvez le constater sur la photo ci-dessus, la carte du Pi correspond, à 1 mm près, à 7 x 11 plots de Légo. Ce qui fait que la carte tient parfaitement en place, avec très peu de jeu. Le circuit Velleman VM188 occupe lui 6 x 10 plots. L’ensemble tient tout juste sur une hauteur de 3 rangées de Légo. On pourrait penser que ça a été fait pour 😉

Il ne reste qu’à placer le couvercle, et à positionner le régulateur de tension 🙂

imageIMPORTANT : Lorsque l’on abaisse une tension comme ici, en passant de 12V à 5V, il y a une partie de l’énergie qui est « absorbée » par le circuit. Pour connaître la puissance absorbée ou « dissipée » en Watt par le circuit, il faut faire la différence entre la puissance du courant de sortie et celle du courant d’entrée P diss = P out – P in. On obtient la puissance en faisant le produit de la tension et de l’intensité du courant P = U x I. Si on considère que le Pi tire environ 700 mA, soit 0,7 A, on obtient P diss = 12 x 0,7 – 5 x 0.7 = 4,9 W. Sur ces 4,9 W de puissance dissipée, une partie sera convertie en chaleur, c’est l’effet Joule. Ce qui veut dire que le module va chauffer ! C’est pour cela que le régulateur de tension  KEMO M015N est équipé d’une patte en métal qui sert de radiateur, et  qui peut être vissé à un radiateur plus grand en cas de besoin. Sur la datasheet du régulateur de tension  KEMO M015N il est précisé qu’au-delà de 3W dissipé, il est conseillé de laisser respirer le module et de ne pas l’enfermer. Au-delà de 6W, il est vivement recommandé d’utiliser un radiateur pour refroidire le module.

Comme le régulateur de tension  KEMO M015N risque de chauffer, je vais le positionner au dessus du boitier, en laissant bien respirer la patte en métal. Voici le résultat final :

image

Tout à fait à gauche, le domino qui fera le lien avec le faisceau électrique de l’allume-cigare. A droite, la carte son prête à être connectée à l’autoradio 🙂

Baptisé Carpo (en référence à une lune de Jupiter), le module est presque complet. Il me reste le circuit capacitif pour l’extinction du dispositif à ajouter 🙂

8 – Alimentation du Pi dans la voiture 2/3 – Allumage

Au démarrage de la voiture

Le problème

Si nous branchons simplement le Pi sur un abaisseur de tension, que se passe-t-il lorsque nous entrons dans notre voiture et démarrons le moteur :

  1. On entre la clé, et on met le contact (petit pique de tension)
  2. A ce moment là, du courrant est disponible sur le circuit électrique et le Pi boot
  3. On tourne la clé à fond pour démarrer le moteur (grosse chute de tension + parasites)
  4. Là, l’appel de courant du démarreur est tel que plus rien n’est disponible pour le Pi, qui s’éteint, privé de courant
  5. Le moteur tourne (tension relativement stable selon usage des équipements de la voiture)
  6. Du courant est à nouveau disponible, et le Pi boot à nouveau

Pour faire simple, notre pauvre Pi s’en prend plein les carreaux… Subissant piques et chutes de tension, parasites, multiple boots et extinctions sauvages… On peut s’attendre à réduire considérablement la durée de vie du Pi, voir à sérieusement l’endommager dès les premières utilisations.

La solution

La première chose à faire est de choisir un régulateur de tension continue stabilisé et anti-parasite. Ceci permettra d’absorber les variations importantes du courant pour fourrnir une tension propre et stable au Pi.

imageLe régulateur de tension  KEMO M015N disponible chez Conrad notamment, répond à ces exigeances. Il permet de choisir la tension de sortie voulue (5V pour  le Pi), et accepte une tension d’entrée entre 6V et 28V (parfait pour les 12V fournis par la prise allume cigare).

Ensuite, il faut empêcher le Pi de booter lorsque l’on met le contact avant de démarrer le moteur, et le démarrer seulement une fois que le moteur tourne. Pour cela, il convient d’ajouter un petit circuit de tempo équipé d’un relais, qui n’autorisera l’arrivée du courrant au Pi qu’au bout de quelques secondes.

ar-module-vm188-377C’est alors que j’ai repensé au circuit de tempo Velleman VM188 que j’aurais pu utiliser pour mon projet Pi TimeLapse. En effet, ce petit circuit alimenté en 12V (comme c’est pratique 😉 ), peut être utilisé de plusieur façons, et notamment, déclancher le relais après un nombre de secondes déterminé. En déclenchant le relais au bout de 10 secondes, le Pi ne sera pas alimenter entre le moment où vous mettez le contact et le moment où vous démarrez le moteur.

Un mot sur le circuit de tempo Velleman VM188 : Quelque soit votre projet, si vous avez besoin d’une minuterie à sortie sur relais, c’est LE kit qu’il vous faut ! Vous pouvez le trouver entre 15€ et 20€ (pas donné mais très fonctionnel…), et faire fonctionner sur des temps allant de 1 seconde à 60 heures selon 4 modes :

  • démarrage avec impulsion (démarre allumé)
  • démarrage avec pause (démarre éteint)
  • activation et désactivation unique (s’allume ou s’éteint après un certain temps)
  • fonctionnement cyclique (s’allume puis s’éteint en boucle, on peut choisir la durée d’allumage et la durée de coupure pour chaque cycle)

Le relais équipé est prévu pour des courants de 230V sur 16A, vous avez donc de quoi programmer l’alimentation tout ce que vous souhaitez dans votre maison, voiture, panneaux solaires,…

Voici le schéma complet du montage :

UntitledAvec ce montage, j’assure à mon Pi une alimentation propre, sans danger pour ma voiture 🙂

Reste à gérer la coupure brutale de courant lorsqu’on éteint le moteur…