Bon, alors pour ceux que ça intéresserait, je suis en train de finaliser une nouvelle version de la galerie d’images Mozaic, qui va être plus… comment dire… Non-javascript friendly, et avec moins de fioritures scriptaculous.
Je viens présenter la 1ère application concrète développée autour du framework Mozaic.
C’est une idée que j’avais eue il y a un moment, lorsque delicious.com menaçait de fermer, d’être racheté, enfin on ne savait pas trop. En tant que fervent utilisateur, je me suis dit qu’il serait intéressant de développer ma propre plateforme de stockage centralisé de bookmarks.
Je l’avais commencé avant d’avoir développé Mozaic, et la chose s’était tassée.
Là, comme Mozaic est stable, je m’y suis relancé, et voilà le résultat.
Fonctionnalités :
- multi-utilisateurs
- tags
- bookmarklet intégrable dans le navigateur, pour l’ajout rapide d’un nouveau bookmark
- système de gestion d’accès aux fonctionnalités (hérité du framework Mozaic)
- import de bookmarks depuis delicious ou firefox (pas testé avec d’autres navigateurs, mais l’implémentation est simple)
C’est un sujet sensible… Si je souhaite développer un site, public ou privé, en PHP, comment m’y prendre ?
Alors pour des choses standards, des blogs par exemple, il existe moultes plateformes : WordPress, Drupal, Dotclear, Joomla (soyons fous).
Pour ma part, pour mon autre blog flexqueries.org, j’ai commencé avec Dotclear. C’était avant la version 2, et les fonctionnalités et extensions étaient assez limitées.
Puis est arrivé Joomla. Pratique, modulaire, mais… Lourd, vraiment lourd. Des temps de réponse vraiment pas top, et une architecture certes polyvalente, mais vraiment hardcore.
Il tourne actuellement sous Drupal, qui est un environnement assez riche, que j’ai trouvé un peu difficile à aborder au départ. Mais la documentation et les contributions sont nombreuses, et j’ai fini par assimiler les principes.
J’ai même osé écrire mon propre plugin, un truc qui affiche le contenu d’un répertoire du serveur dans une page…
Enfin, j’utilise WordPress pour le blog Dirty Marmotte, qui semble finalement être le plus accessible pour les utilisateurs normaux (non informaticiens).
Comme je disais, tous ces environnements sont personnalisables, mais chacun a ses propres conventions, ses techniques de développement, et en ce qui me concerne, ça m’emmerde de devoir tout réapprendre pour chaque environnement, et de passer des heures avant d’arriver à faire un truc très simple, car obligé de passer par l’API. C’est frustrant.
J’ai donc préféré mettre à profit mon expérience de développeur Web pour créer mon propre environnement de développement, sans fioritures. Je l’ai déjà utilisé pour le projet Mozaic, et je l’ai soigneusement enrichi et généralisé.
Pour la structure, je dirais qu’il est MVC-friendly. J’entends par là MVC, mais si on veut faire quelque chose de simple, on n’est pas obligé de se taper le cheminement complet. C’est bête, mais ça peut faire gagner beaucoup de temps.
Pour l’accès à la base de données, j’utilise adodb, dont la qualité n’est plus à prouver.
Pour les templates, j’ai eu une expérience avec Smarty, mais il est assez lourd et surtout, surtout : il ne supporte pas l’héritage. Et ça, ça craint. J’ai donc opté pour Dwoo, et franchement je ne suis pas déçu. L’héritage est vraiment super, et il conserve une compatibilité avec Smarty, donc on n’est pas perdu :)
Pour les curieux, c’est ici : http://dirtymarmotte.net/wiki
Nous avons câblé le capteur, il ne reste plus qu’à écrire le programme qui nous permettra d’obtenir la température, la pression atmosphérique et l’altitude.
La première chose à faire, c’est de pouvoir communiquer avec le module BMP085. Nous aurons besoin de 2 fonctions de lecture, pour lire respectivement des valeurs de 8 et 16 bits.
#include <Wire.h> //L'adresse I2C du BMP085 #define BMP085_ADDRESS 0x77 // Lit 1 byte du BMP085 à 'address' char bmp085ReadChar(unsigned char address) { unsigned char data; Wire.beginTransmission(BMP085_ADDRESS); Wire.write(address); Wire.endTransmission(); Wire.requestFrom(BMP085_ADDRESS, 1); while(!Wire.available()); return Wire.read(); } // Lit 2 bytes sur le BMP085 // Le premier byte vient de 'address' // Le second byte vient de 'address'+1 int bmp085ReadInt(unsigned char address) { unsigned char msb, lsb; Wire.beginTransmission(BMP085_ADDRESS); Wire.write(address); Wire.endTransmission(); //on va lire 2 bytes, correspondant à un integer Wire.requestFrom(BMP085_ADDRESS, 2); //Attendons que les 2 bytes soient arrivés... while(Wire.available()<2); msb = Wire.read(); lsb = Wire.read(); return (int) msb<<8 | lsb; }
Nous sommes à présent capables de lire des valeurs du module. Cool! On va en avoir besoin pour lire les 11 coefficients de calibration, stockés dans l’EEPROM du BMP085. Ces valeurs vont nous permettre de calculer la pression absolue. Il suffit de les lire une seule fois, au début du programme. Nous allons les mettre dans la fonction setup().
// paramètre d'oversampling 0 à 3, //qui permet d'avoir plus de précision dans les mesures const unsigned char OSS = 0; // Valeurs de calibration int ac1; int ac2; int ac3; unsigned int ac4; unsigned int ac5; unsigned int ac6; int b1; int b2; int mb; int mc; int md; // b5 est calculé dans bmp085GetTemperature(...), // et est aussi utilisée dans bmp085GetPressure(...) // donc ...Temperature(...) doit être appelée avant ...Pressure(...). long b5; short temperature; long pressure; // Stocke toutes les valeurs de calibration du BMP085 dans des variables globales. // Ces valeurs sont nécessaires pour calculer la température et la pression. // Cette fonction doit être appelée au début du programme. void bmp085Calibration() { ac1 = bmp085ReadInt(0xAA); ac2 = bmp085ReadInt(0xAC); ac3 = bmp085ReadInt(0xAE); ac4 = bmp085ReadInt(0xB0); ac5 = bmp085ReadInt(0xB2); ac6 = bmp085ReadInt(0xB4); b1 = bmp085ReadInt(0xB6); b2 = bmp085ReadInt(0xB8); mb = bmp085ReadInt(0xBA); mc = bmp085ReadInt(0xBC); md = bmp085ReadInt(0xBE); }
void setup() { Serial.begin(9600); Wire.begin(); bmp085Calibration(); }
Une fois que les valeurs de calibration sont lues, il nous faut encore deux variables pour calculer la température et la pression : ut et up. Ce sont les valeurs de température et pression non compensées, notre point de départ pour déterminer les valeurs réelles de température et pression. A chaque fois qu’on veut obtenit la température ou la pression, il faut lire au préalable ces valeurs.
La température non compensée est sur 16 bits (type int), la pression sur 32 bits (type long).
// Lit la température non compensée unsigned int bmp085ReadUT() { unsigned int ut; // Ecrit 0x2E dans le registre 0xF4. // Cela demande une lecture de température. Wire.beginTransmission(BMP085_ADDRESS); Wire.write(0xF4); Wire.write(0x2E); Wire.endTransmission(); // Attendons au moins 4.5ms delay(5); // Lit les 2 octets des registres 0xF6 et 0xF7 ut = bmp085ReadInt(0xF6); return ut; } // Lit la pression non compensée unsigned long bmp085ReadUP() { unsigned char msb, lsb, xlsb; unsigned long up = 0; // Ecrit 0x34 + (OSS<<6) dans le registre 0xF4 pour // demander une lecture de la pression avec le paramètre d'oversampling Wire.beginTransmission(BMP085_ADDRESS); Wire.write(0xF4); Wire.write(0x34 + (OSS<<6)); Wire.endTransmission(); // Attend la fin de conversion, le délai dépend de OSS delay(2 + (3 << OSS)); // Lit la réponse dans les registres : // 0xF6 (MSB), 0xF7 (LSB), et 0xF8 (XLSB) Wire.beginTransmission(BMP085_ADDRESS); Wire.write(0xF6); Wire.endTransmission(); Wire.requestFrom(BMP085_ADDRESS, 3); // Attend aue les données soient dispo (3 bytes) while(Wire.available() < 3); msb = Wire.read(); lsb = Wire.read(); xlsb = Wire.read(); up = (((unsigned long) msb << 16) | ((unsigned long) lsb << 8) | (unsigned long) xlsb) >> (8-OSS); return up; }
Dans ces deux fonctions, nous utilisons la fonction delay() pour laisser le temps au BMP085 de terminer ses traitements.
Le paramètre d’oversampling (OSS) indique au capteur de calculer une moyenne de plusieurs mesures, afin d’avoir une précision accrue. Ici, il est désactivé.
La durée d’attente est le maximum indiqué dans le datasheet du module, mais nous pourrions à la place nous baser sur le pin EOC (End Of Conversion) pour connaitre avec précision le moment où le BMP05 a terminé de lire les données. Tant qu’il travaille, le pin EOC est à l’état LOW, et dès qu’il a terminé, il passe à HIGH.
Nous avons toutes les variables requises pour calculer la température et la pression. Dans le datasheet, une formule assez cool nous donne la température, et une autre, beaucoup, beaucoup plus barbue, nous donne la pression.
Merci à Jimbo, chez Sparkfun, d’avoir transcrit tout ça en C, ça fait vraiment plaisir :)
// Calcule la température à partir de ut. // La valeur de sortie est exprimée en dixièmes de degrés. short bmp085GetTemperature(unsigned int ut) { long x1, x2; x1 = (((long)ut - (long)ac6)*(long)ac5) >> 15; x2 = ((long)mc << 11)/(x1 + md); b5 = x1 + x2; return ((b5 + 8)>>4); } // Calcule la pression à partir de up. // Les valeurs de calibration doivent être initialisées. // b5 est aussi requise, donc bmp085GetTemperature(...) doit être appelée en premier. // La valeur de sortie est exprimée en Pascals. long bmp085GetPressure(unsigned long up) { long x1, x2, x3, b3, b6, p; unsigned long b4, b7; b6 = b5 - 4000; // Calcule B3 x1 = (b2 * (b6 * b6)>>12)>>11; x2 = (ac2 * b6)>>11; x3 = x1 + x2; b3 = (((((long)ac1)*4 + x3)<<OSS) + 2)>>2; // Calcule B4 x1 = (ac3 * b6)>>13; x2 = (b1 * ((b6 * b6)>>12))>>16; x3 = ((x1 + x2) + 2)>>2; b4 = (ac4 * (unsigned long)(x3 + 32768))>>15; b7 = ((unsigned long)(up - b3) * (50000>>OSS)); if (b7 < 0x80000000) p = (b7<<1)/b4; else p = (b7/b4)<<1; x1 = (p>>8) * (p>>8); x1 = (x1 * 3038)>>16; x2 = (-7357 * p)>>16; p += (x1 + x2 + 3791)>>4; return p; }
Bon alors là, on est pas mal ! Encore une fonction pour calculer l’altitude à partir de la pression :
//Retourne l'altitude théorique //en fonction de la pression atmosphérique float getAltitude(long pressure) { // Pression au niveau de la mer (Pa) const float p0 = 101325; float altitude = (float)44330 * (1 - pow(((float) pressure/p0), 0.190295)); return altitude; }
Calculons tout ça dans la boucle principale, et envoyons les résultats dans le port série:
void loop() { temperature = bmp085GetTemperature(bmp085ReadUT()); pressure = bmp085GetPressure(bmp085ReadUP()); float altitude = getAltitude(pressure); Serial.println("***********************"); Serial.print("Temperature: "); Serial.print(((float)temperature / 10), 1); Serial.println(" deg."); Serial.print("Pression: "); Serial.print(pressure, DEC); Serial.println(" Pa"); Serial.print("Altitude: "); Serial.print((int)altitude, DEC); Serial.println(" m"); delay(1000); }
Et voilà la sortie, au chalet, par cette soirée de février :
*********************** Temperature: 22.3 deg. Pression: 90739 Pa Altitude: 921 m ***********************
Ce qui est assez réaliste aujourd’hui, puisqu’on estime être à 930 m rééls. Mais selon la météo, on se retrouve dans une fourchette de 870 à 1000 et quelques…
Je tiens une fois encore à remercier l’article de SparkFun, dont cet article est essentiellement une traduction. Je vous invite à le lire pour plus de précisions. Sans lui, je me serais tapé la tête contre le lambris… J’y serais probablement arrivé, mais en beaucoup plus de temps :)
Je n’ai jamais aucune inquiétude habituellement lorsque je mets à jour mon Linux (je parle d’upgrade du système, pas des mises à jour ponctuelles).
Ben là, je me suis bien fait avoir. Je passant à la 11.10, Oneiric Ocelot, je me suis retrouvé, après le reboot, face à un écran noir. Je ne me démonte pas, je reboot à nouveau en mode récupération, pour voir ce que raconte mon fichier de config X11 (j’ai un 2ème écran connecté sur le portable, je me suis dit que le problème pourrait être causé par cette particularité).
Donc, je vais pour restaurer l’ancien fichier par la console, quand… je m’aperçois que le mode récupération charge le système de fichier en lecture seule! Trop pratiiique!
Je commence à m’agacer gentiment, finalement je suis obligé de downloader l’ISO d’Ubuntu, de le graver, et de booter dessus. Bon…
Je démarre sur le live CD, je fais mes bidouilles et finalement ça ne marche toujours pas. Gnarf!!! Mais je ne vais quand même pas le réinstaller! Si…
Je n’ai pas confiance dans la réinstallation douce “par dessus l’existant”, alors je sauvegarde mes dossiers importants sur un disque externe.
BIEN M’EN A PRIS! Parce que la récupération “douce” du système m’a peut être épargné les fichiers dans /home/nico, mais le /var/www a été tout nettoyé. Non mais franchement… Pareil, mes applications ont été supprimées, mes tâches cron, et je vais en découvrir d’autres…
Bon alors certains vont me dire “ben c’est le fonctionnement normal…”
C’est pas une raison!
Ça y est, j’ai reçu mon Arduino Uno ! Je l’ai commandée chez Lextronic, livrée en 1 semaine et demie.
Dans la foulée, comme ces salopards n’offrent pas les frais de port, même pour une commande de plus de 100€, je me suis équipé en capteurs et composants divers pour amortir lesdits frais de port.
En même temps, me direz-vous, à quoi servirait une carte Arduino sans capteurs et composants divers? A rien. Bon, on est d’accord.
Du coup, j’ai commandé un capteur qui fait thermomètre et baromètre, le BMP085.

Et l’altimètre dans tout ça ? Hé bien il existe une formule qui lie la pression atmosphérique à l’altitude, elle est présentée ici.
Comme on peut le constater, cette formule utilise une pression de référence de 1013,25 hPa au niveau de la mer. Ce qui signifie que l’altitude calculée ne pourra jamais être précise et va inexorablement varier, car nous ne vivons pas dans une bulle avec une pression régulée…
Par beau temps, l’altitude calculée sera inférieure à celle calculée par mauvais temps (anticyclone vs. dépression).
Mais bon, nous ne cherchons pas à connaitre l’altitude au mètre près! Sinon, il faut s’orienter vers un module GPS, qui fournira une information bien plus précise sur l’altitude. Maiiiis pas la température ni la pression atmosphérique. Faut faire des choix dans la vie.
La carte Arduino et le module BMP085 vont discuter selon le protocole I2C, et c’est la classe Wire fournie dans la bibliothèque d’Arduino qui va nous aider dans cette discussion.
Avant tout, je tiens à préciser que les informations proviennent essentiellement du tutorial du BMP085, chez SparkFun. Je me suis hautement appuyé sur eux pour mettre en place ce montage.
Alors en fait, le capteur possède une petite EPROM qui contient des valeurs d’étalonnage. Ces valeurs vont nous servir de référence pour calculer les valeurs réelles de pression et température.
Pour extraire ces valeurs de référence, avec la librairie Wire, on donne l’adresse de la valeur dans l’EPROM, le nombre d’octets à extraire(typiquement 1 ou 2) et hop.
Prêt ? on y va.
Alors tout d’abord, commençons par le montage. Il faut alimenter le capteur, mais alors attention: la tension maximale acceptée est de 3.6V ! Utilisons donc la tension régulée à 3.3V fournie par notre carte Arduino. Nous allons donc connecter la masse à la borne GND et le 3.3V à la borne VCC du capteur.
Pour la communication entre la carte Arduino et la bébête, nous allons relier respectivement la borne SDA (Serial DAta) du capteur au pin A4 de l’Arduino (oui oui, dans les entrées analogiques), et la borne SCL (Serial CLock) au pin A5.
Les pins A4 et A5 de l’Arduino sont utilisés par défaut par la classe Wire pour les données et le signal d’horloge.
Pour la faire courte, donc :
VCC -> 3.3V
GND -> masse
SDA -> A4
SCL -> A5
Dans le prochain article, je vais vous présenter le programme qui permet d’exploiter l’engin.
J’avais pensé me fabriquer mon propre bureau. Mais comment faire, par où commencer?
En lisant mes flux RSS, je suis tombé sur cet article LifeHacker. Et là, j’ai dit “je veux le même ! Il est monstrueux.”
J’ai contacté le type par le biais de la messagerie de Flickr pour savoir s’il voulait bien me fournir les plans, mais pas de réponse. Dommage pour moi.
Alors je ne me suis pas démonté, et j’ai fait mes propres plans avec SolidWorks.
Tout est en pin, et les vitres sont en verre dépoli, c’est super classe. Je n’ai pas mis de lumières à l’intérieur, je réfléchis encore aux conséquences jackyennes de la chose…
Bref, voici quelques clichés, et si les plans vous intéressent… aucun problème :)
J’ai trouvé une meilleure option pour le DNS dynamique réel. C’est plus pratique à utiliser que le bricolage précédent.
J’ai ouvert un compte chez http://freedns.afraid.org/, qui me permet d’avoir un nom de domaine associé à mon IP de la maison (comme chez dyndns.com, mais sans expiration au bout de 30 jours d’inactivité). Donc, pour accéder à la maison, j’ai une adresse du genre dirtymarmotte.homenet.org (peu importe le domaine).
Ensuite, dans les paramètres de mon domaine dirtymarmotte.net, j’ajoute un sous-domaine, par exemple “chezmoi.dirtymarmotte.net”, qui est un enregistrement CNAME vers dirtymarmotte.homenet.org. Un synonyme, quoi.
Avec tout ça, j’ai un DNS dynamique qui me convient.
Il ne faut cependant pas oublier d’appeler la page de mise à jour de l’IP chez afraid.org pour tenir l’adresse à jour. Pour cela, une tâche cron effectue un wget vers cette page toutes les 30 minutes et je suis tranquille.
Tout ce que je voulais, c’était copier un ou plusieurs dossiers de photos sur un serveur FTP, et les rendre immédiatement visibles par une page web dynamique, sous une forme qui ait de la gueule :
Première tentative avec une table HTML générée dynamiquement, mais ce n’est pas très adaptable à de multiples résolutions, et encore moins au redimensionnement.
Au final, j’ai préféré générer des balises <div>, contenant les miniatures, et positionner ces <div> par javascript, mais attention, encore une fois : de la gueule, bordel !
Donc, direction http://script.aculo.us/ et leurs incroyables fonctionnalités d’animation.
En ce qui me concerne, un simple Effect.move() me satisfait amplement.
Les miniatures sont donc positionnées dynamiquement ET joliment, en fonction de la largeur d’écran disponible.
Et pour zoomer sur une image, merci à lightbox, c’est du beau boulot :)
Ça, c’était pour la partie client ; je n’ai fait qu’assembler des briques existantes de façon… disons harmonieuse. Avec un peu de CSS pour faire joli.
Côté serveur, c’est évidemment en PHP, et il y a eu un petit peu plus de boulot.
J’ai repris mon framework maison habituel, avec comme moteur de template Dwoo (tip top, et bien plus léger que Smarty)
Comme il n’y a qu’une page, c’est peut être un peu masturbatoire de mettre en place cette structure (même si elle reste légère), mais au moins elle est là pour la suite si j’ai des envies futures.
Bref ! Dans ce type d’application, il est à mon humble avis judicieux de générer des miniatures pour l’affichage dans la galerie. Pas question de faire télécharger aux clients des images full-size pour les afficher à 200 pixels de large…
Donc, une petite fonction génère un cache de miniatures sur le serveur, et ce sont ces miniatures qui sont envoyées pour l’affichage global de la galerie. Et si l’utilisateur veut grossir une des images, alors on peut lui envoyer une image plus grande (dans mon cas, je n’envoie pas l’image full-size non plus, mais une image à 800px). Et si VRAIMENT on veut pouvoir accéder à l’image full size, il n’y a qu’à générer un lien par image.
Côté sécurité, il y a une protection contre le path traversal, c’était la moindre des choses.
Voici le résultat : http://mozaic.dirtymarmotte.net. Appréciez le comportement quand on redimensionne la fenêtre…
Le but initial de ma manœuvre est de pouvoir connaitre l’IP externe de la maison à tout moment, afin d’accéder à un service quelconque sur une machine de mon réseau local.
Le concept est assez simple :
- j’ai un script PHP sur le serveur qui héberge ce blog.
- j’envoie régulièrement (disons toutes les 10 minutes) une requête http sur ce script depuis la maison.
- le script côté serveur récupère l’IP qui l’appelle, et la stocke quelque part (base de données, fichier…)
- depuis une page du serveur, je peux désormais accéder à l’IP de chez moi !
Côté serveur, basiquement voici la chose. Je l’ai appelé ping.php.
//Récupère l'IP du client :
$clientIp = $_SERVER["REMOTE_ADDR"];
//Enregistre l'IP dans un fichier texte.
$f = fopen("ip", "w");
fwrite($f, $clientIp);
fclose($f);
C’est tout… Pour le moment. Parce que si jamais un petit rigolo s’amuse à ouvrir cette page, c’est son IP qui va être mémorisée, et du coup ce n’est pas top. Avec une bonne vieille authentification HTTP, le problème sera résolu.
Côté client, on va avoir besoin d’un script shell qui va appeler ping.php à intervalles réguliers. Quelque chose comme :
#!/bin/sh #La boucle est infinie while [ 1 -ne 0 ] do #Charge la page wget http://dirtymarmotte.net/redir/ping.php #Efface le fichier téléchargé, pour être propre rm ping.php #On y retournera dans 10 minutes sleep 600s done
Notez qu’on pourrait tout aussi bien créer une tache cron pour lancer le wget.
A partir de là, nous avons tout ce qu’il faut pour effectuer une redirection vers la maison, depuis une page de notre serveur.
Un peu de PHP :
<?php
//Lit l'adresse de la maison
$ip = file_get_contents("ip");
//Construit l'URL pour pointer sur la webcam
$url = "http://$ip:8081";
?>
Et sur la même page, un peu de HTML pour charger la page dans un iframe:
<html> <body> <div><?php echo $url ?></div> <iframe width="100%" height="100%" src="<?php echo $url ?>"> </iframe> </body> </html>
Et en plus, ça fonctionne ! on peut même envisager une redirection http avec un en-tête Location.
Bon, ce n’est pas vraiment du DNS dynamique, car le système se limite à un accès par navigateur. Mais en tout cas, cela me permet de connaître à tout moment l’IP de la maison, et à partir de là, je peux établir une connexion arbitraire (ssh, ftp, ou ce que je veux).






