Données d’un capteur vers un serveur Web

Introduction

Lors de mon précédent article, j’évoque l’envoi des données via Wifi depuis un ensemble de composants autour d’une carte Arduino. Or, la programmation n’est pas très facile pour donner plus d’intelligence à ces capteurs. Je suis donc reparti du capteur de température et de pression DHT11, mais relié à un Raspberry PI.


DHT11 - Cảm biến nhiệt độ và độ ẩm https://images.duckduckgo.com/iu/?u=http%3A%2F%2Fwww.openmediacentre.com.au%2Ffileadmin%2Fuser_upload%2Ftx_onqcatalogue%2Fproduct%2Fraspberry-pi-model-b_80_thumbimg1.jpg&f=1
(les photos ne sont pas à l’échelle)
Prix des composants :

  • Raspberry Pi 2 starter kit (avec les câbles, l’alimentation, une carte SD, des câbles, des radiateurs, un boitier, etc) : 75 € sur Amazon
  • DHT11 (capteur nu) : 2.20 € sur Amazon
  • Dongle Wifi (sur port USB) : quelques Euros (sur eBay)

La programmation est faite en script SH et Python dans le Raspberry et je dispose d’un serveur PHP qui héberge les données (mon PC).

 

Vue du Raspberry avec les câbles pour clavier, souris, écran (inutiles en mode normal, mais nécessaire pour la programmation) + le composant DHT11, relié par 3 fils.

 

Vue des composants minimum pour que les données soient envoyées après avoir tout programmé. Il faudra juste prévoir une boite pour protéger le Raspberry et laisser le capteur « à l’air libre ».

 

Protocole de communication – 1. Souscription

Le Raspberry dispose d’un Dongle Wifi pour se connecter au serveur. J’utilise donc l’adresse MAC du Wifi pour souscrire au serveur selon ce principe :

Dans ces échanges, l’adresse MAC sert de clé. Chaque unité de communication devrait avoir une adresse MAC unique sur le réseau.

Une fois le capteur identifié sur le serveur, il n’est pas encore actif. Le serveur a un capteur en attente de paramétrage manuel (donner un nom compréhensible par un humain, un picto, un groupe, une catégorie, etc…). Tant que cette étape n’est pas faite, les données ne sont pas transmises. Le capteur passe à l’état « Activé » lorsque le paramétrage est terminé.

 

Protocole de communication – 2. Envoi des données

Le serveur est maintenant prêt à réceptionner les données du capteur.

Dans le cas du DHT11, il y a 2 données à chaque envoi : température (en °C) et humidité (en %). Le Raspberry ne fait qu’un appel au serveur pour ces 2 informations.

 

Montage électronique

Le montage sur les PINs du Raspberry sont faits directement car le DHT11 accepte un voltage de 3.3v, fourni par le PIN numéro 1. Si cette alimentation directe n’est pas assez stable, on pourra utiliser un régulateur de tension (AMS1117, 4.70 € les 10 pièces sur Amazon), prenant en entrée du 5v et donnant en sortie du 3.3v stabilisé (monté avec un condensateur de 470µF). Ce sera surtout le cas si on utilise une autre source d’énergie qu’une prise de courant (pile, accu, batterie de voiture, capteurs solaires, etc).

 

Le DHT11 comme capteur

Le DHT11 est un composant assez instable, qui ne garanti pas toujours la bonne lecture des données. Il semble que d’autres capteurs soient plus efficaces, peu onéreux, fonctionnant avec une plus grande plage de voltage : TMP36GT9, DS18B20, etc.

Le DHT11 est par contre bien documenté, pour les bidouilleurs comme moi, devant programmer en Python.

 

Copies d’écran du serveur

Liste des capteurs

 

 

Détail d’un capteur d’humidité

 

Pour aller plus loin

A partir de ce système, il est possible d’équiper toute la maison, d’avoir des capteurs partout et de suivre les courbes. Le budget à prévoir reste collé au prix du Raspberry (carte nue à 40 €), car les capteurs ne coûtent que quelques Euros. D’autres solutions voient le jour, avec des cartes électroniques programmables, comme TESSEL (avec Wifi intégré) : https://www.tessel.io/

D’autres capteurs pourront être ajoutés, sous réserve de pouvoir lire les données (sur le même principe), depuis les ports GPIO du Raspberry. Grâce au grand nombre de PINs disponibles, on pourrait ajouter d’autres capteurs à un même Raspberry, recueillant ainsi un lot de données. Pour une pièce de la maison : température, humidité, ensoleillement, concentration CO2, odeur (oui, pour faire remonter le niveau de puanteur du placard à chaussures), méthane, détecteur de mouvement, etc.

 

Raspberry : un feu de cheminée ou un aquarium

Introduction

Noël approche, qui n’a pas rêvé d’avoir un feu de cheminée. Je vous propose de transformer votre Raspberry en « boite à diffuser une vidéo de distraction », comme un feu de cheminée, un aquarium ou une jolie vue de ruisseau.

 

Configuration du Raspberry

Rien de plus qu’un Raspberry qui fonctionne et qui est connecté à un écran.

 

Choix de la vidéo

Youtube fourni un grand nombre de vidéos, en HD de feu de cheminée ou d’aquarium. Voici mon choix :

https://www.youtube.com/watch?v=0fYL_qiDYf0

La vidéo dure 2 heures, mais on n’a besoin de tout le fichier. Il faut passer par une outil de téléchargement (cherchez « youtube downloader firefox plugin ») pour récupérer le fichier MP4.

 

Extraction d’une portion de quelques minutes

Passez par ffmpeg pour extraire une partie de la vidéo. Notez la période : début et durée, puis lancer la ligne de commande suivante pour l’extraction :

ffmpeg -ss 00:30:00 -i feuDeCheminee-full.mp4 -t 00:02:00 -vcodec copy -acodec copy feu-portion.mp4

Sur la vidéo « feuDeCheminee-full.mp4 », je saute 30 minutes du début et je prends 2 minutes de vidéo finale. Le fichier « feu-portion.mp4 » contiendra le résultat : une vidéo de quelques dizaines de Mo (au lieu de plus d’un Go pour la vidéo complète).

 

Lire la vidéo avec le Raspberry

Tentez de lire la vidéo avec VLC : c’est très lent. Impossible de choisir cette solution. En allant voir sur le Net, le player conseillé est « omxplayer ». Cherchez à l’installer avec les bibliothèques pour être sûr du résultat.

Mon Raspberry est configuré en point d’accès Wifi, avec une application Web pour le piloter (depuis mon smartphone ou depuis un PC). J’ajoute cette fonctionnalité, en appelant un script pour lancer la vidéo :

#!/bin/bash

export DISPLAY=:0.0

SERVICE='omxplayer'
RES=0
trap "exit" INT
while [ $RES -eq 0 ]; do
  if ps ax | grep -v grep | grep $SERVICE > /dev/null
  then
    sleep 1
  else
    xterm -fullscreen -fg black -bg black -e omxplayer --win "0 0 1600 1200" -r $1
    RES=$?
    echo "RES: $RES"
  fi
done

xrefresh -display :0

 

Vous constaterez des lignes particulières (que j’utilise peu) :

trap "exit" INT

Cela signifie que si un sous-script est lancé et qu’il se fait killer, le script s’arrête aussi.

 

xrefresh -display :0

A la fin du script, je force le rafraîchissement de l’écran pour nettoyer ce qui peut rester.

 

xterm -fullscreen -fg black -bg black -e omxplayer --win "0 0 1600 1200" -r $1
RES=$?

La ligne de commande principale, qui lance la vidéo, dans un terminal (xterm), en plein écran, avec omxplayer en mode fenêtre, d’une taille de 1600×1200. La variable RES récupère le code retour du player. Si elle vaut 0, on boucle, sinon, on s’arrête (le CTRL-C fait un RES = 2).

Pour simuler la lecture sans fin, j’ai une boucle while autour de tout ça.

 

Arrêt de la lecture par un script

J’ai aussi un autre script qui arrête la lecture en boucle :

PID=$(ps aux | grep omxplayer | awk {'print $2'})
kill -9 $PID 2>/dev/null

xrefresh -display :0

Rien de très novateur. J’ai essayé la commande « killall -9 omxplayer », mais elle n’est pas satisfaisante.

Mon interface (depuis un PC) pour lancer quelques vidéos et un bouton en bas pour arrêter :

Arduino : premiers essais

Introduction

Après avoir complètement paramétré mon Raspberry Pi en point d’accès wifi, avec une webcam et une application web pour le piloter depuis mon smartphone (article complet), je voulais essayer jouer avec les PIN du GPIO. Mais je ne voulais pas refaire un autre Raspberry pour jouer avec les « sensors » (détecteur de température, détecteur de mouvement, etc.). Pour faire ça, il y a Arduino, en plus simple et plus économique…

 

 

Installation de l’environnement

Depuis longtemps, je suis habitué à l’IDE Eclipse pour mes développements. J’ai pris mes réflexes et je pense être moins désorienté dans ces environnement. C’est pour cette raison que j’installe un Eclipse dédié.

J’ai donc suivi quelques tutoriels, mais celui-ci me semble très complet pour tout savoir sur l’interface entre ma machine et la carte Adruino : http://www.codeproject.com/Articles/1003347/Creating-Arduino-programs-in-Eclipse

Avec Ubuntu, le port est à changer. Pour savoir quel port il faut configurer (« //./COM15″ dans le tutoriel), voici les étapes, depuis un terminal :

  1. Branchez votre carte Arduino sur le port USB et tapez « lsusb ». Si une ligne apparaît avec « Arduino », c’est bon, la carte est reconnue.
  2. Tapez « dmesg ». Vous devriez voir :
    [ 1013.247248] usb 8-2: Product: Arduino Uno
    [ 1013.247250] usb 8-2: Manufacturer: Arduino Srl
    [ 1013.247253] usb 8-2: SerialNumber: 85431303636351D0A101
    [ 1013.272419] cdc_acm 8-2:1.0: ttyACM0: USB ACM device
  3. Tapez « ls /dev/ttyACM* ». Vous devriez avoir le port de l’Arduino : /dev/ttyACM0
  4. Il reste à permettre à l’utilisateur d’écrire dans ce device : sudo chmod 777 /dev/ttyACM0

 

Premier script : « blink »

En suivant le tutoriel, je crée le programme « blink.cpp » en copiant / collant le code fourni, mais une erreur survient sur l’include :

#include "arduino.h"

 

Il faut la changer en :

#include "Arduino.h"

En consultant le fichier recherché dans mon code (version 1.6.6 d’Arduino), il y a un « A » (majuscule).

Le build se passe correctement et l’installation sur la carte aussi. La LED clignote bien.

 

Conclusion

Même si l’IDE fourni par Arduino fonctionne très bien, il m’a fallu un peu plus d’une heure pour installer quelques plugins et adapter le dernier. J’espère que cet article fera gagner du temps à ceux qui essaieront.

 

OpenCV pour mesurer la qualité des images – update

Introduction

Un premier article (OpenCV pour mesurer la qualité des images) a été rédigé il y a fort longtemps sur la mesure de qualité d’une image ou d’une photo, par rapport au flou ou au défaut de mise au point. A vrai dire, je n’étais pas complètement satisfait du résultat, en comparant 2 photos ayant de légères différences de netteté. Après d’autres recherches et tests, voici une version nettement améliorée.

 

Le script v2

import cv2, sys

filepath = sys.argv[1]
filename = sys.argv[2]

myCamera = cv2.VideoCapture(0)
if myCamera.isOpened():
    ret, frame = myCamera.read()
    gray = cv2.cvtColor( frame, cv2.COLOR_BGR2GRAY)
    variation = cv2.Laplacian(gray, cv2.CV_64F).var()

    finalFilename = "%s/%d-%s" % (filepath, variation, filename)
    cv2.imwrite(finalFilename, frame)

 

Notes et remarques

Ce script donne un autre résultat, en se basant sur une image en tons de gris et en calculant le laplacien. Ensuite, il calcule la variation sous forme d’un scalaire, donc facilement exploitable pour trier les photos nettes de celles qui le sont moins.

Ce script est utilisé pour un mode « rafale » de prises de vues sur une webcam (derrière un télescope), pour isoler les photos les plus nettes. L’indice de qualité est le préfixe du nom de fichier, donc c’est facile d’avoir les 50 meilleures photos et les 50 les plus floues.

 

Raspberry Pi + Camera : pilotage par un smartphone

Intérêt

Le raspberry est une petite boite contenant l’équivalent d’un PC assez puissant pour faire quelques petites choses intéressantes. L’objectif est de brancher le raspberry sur une source d’alimentation (dans la voiture, près de la télé, sous un télescope) et de piloter quelques actions depuis un smartphone.

Exemples d’application :

  • Près d’un point de surveillance de la maison : Caméra de surveillance, avec enregistrement des vidéos sur une clé USB
  • Dans la voiture : Enregistrement de la conduite de manière automatique. en cas de litiges, vous aurez un enregistrement vidéo
  • Sous un télescope : avec une webcam derrière l’objectif pour faire la mise au point et une série de prises de vue enregistrées sur la clé USB
  • Près d’un moniteur ou d’une télé : pour lancer une présentation PowerPoint / PDF, pour jouer des vidéos, avec la webcam pour prendre des photos

Tout ça, avec un smartphone en guise de télécommande Wifi.

 

Configuration matérielle pour le démarrage

Voici le matériel nécessaire pour configurer le Raspberry complètement :

  • Un clavier USB
  • Une souris USB
  • Un écran
  • Une clé USB
  • Un accès à Internet

 

Configuration logicielle pour le démarrage

  1. Installation de l’image Ubuntu Mate pour Raspberry
  2. Installation des services :
    1. LAMP (Apache + PHP + MySQL), avec changement du user apache
    2. openssh-server (pour accès shell par SSH)
    3. usbmount (auto-mount les clés USB)
  3. Configuration des points de montage : pour la clé USB :  « uid=1000,noauto,user »
  4. Désactivation du firewall Ubuntu
  5. Installation d’un serveur FTP et SSH pour déposer les fichiers depuis un autre PC et lancer des lignes de commandes

 

Configuration logicielle étape 2

Cette étape se fait depuis un PC pour plus de confort. On a donc mis le Raspberry sur un réseau interne :

  • Un PC connecté via RJ45 à un routeur
  • Le Raspberry connecté en RJ45 au routeur

Par la suite, le câble RJ45 sera supprimé.

 

Le clavier, la souris et l’écran peuvent être supprimés après avoir installé les serveurs SSH et FTP.

L’installation d »es logiciels continue :

  1. Installation d’une application web pour accéder au Raspberry depuis un smartphone (PHP avec CodeIgniter) – Réalisée par mes soins ; cf chapitre « Application web pour contrôler le Raspberry » ci-dessous
  2. Changement de la politique de sécurité pour autoriser l’arret et reboot par un utilisateur quelconque (/usr/share/polkit-1/actions/org.freedesktop.login1.policy)
  3. Connexion automatique d’un user (pour ne pas avoir de mire de connexion)
  4. Ajout d’un script à la connexion pour lancer « xhost + » (pour que d’autres machines puissent ouvrir des fenêtres dans la session ouverte)
  5. Activation du dongle Wifi en mode AccessPoint (point d’accès Wifi) et paramétrage du DHCP

 

Application web pour contrôler le Raspberry

Cette application web permettra de piloter le Rasbberry depuis un smartphone. Elle est développée en PHP avec le Framework CodeIgniter. Un template HTML permet d’avoir une charte graphique en Responsive Design : http://binarycart.com/bclivedemos/01-05-2014/v1/bs-binary-admin/index.html

 

Fonctionnalités basiques de l’application PHP :

  • Etat du raspberry : charge machine, type de processeur, version du noyau, version et nom du système (Ubuntu)
  • Lancement de scripts SH (exemple : lancer une présentation avec un fichier PDF précis)
  • Parcours dans les répertoires
  • Caméra : prise de photo, enregistrement d’une vidéo
  • Arrêt et relance du raspberry

 

Utilisation avec un smartphone

Après avoir mis sous tension le raspberry, le smartphone peut s’y connecter par wifi. Un simple navigateur permet d’accéder aux fonctionnalités. Le smartphone devient une télécommande.

 

Copies d’écran sur smartphone

Page de connexion

Page d’accueil : description de la machine

Menu

Page des scripts à lancer

Page des médias USB connectés et détectés

Parcours des fichiers du Raspberry

Page de la gestion de la caméra : prendre une photo, prendre des vidéos

Page d’arrêt / relance du Raspberry

Kivy : gestion de la fin d’une lecture audio

Introduction

Kivy ne propose pas d’interface élaborée pour surveiller le bon déroulement du fichier audio qui est en cours de lecture (durée, temps passé). Mais je vous propose de voir en détail ce qui est exploitable en utilisant le « player » associé à l’objet « son joué ».

 

Structure des données

Pour écouter un fichier MP3 (WAV ou OGG), il faut réaliser ceci :

filename = "youi.mp3"
self.currentSound = SoundLoader.load(filename)
self.currentSound.play()

L’objet « SoundLoader » ne propose pas beaucoup d’interaction. Il est possible de le lire (méthode play), de le forcer à s’arrêter (méthode stop) et de savoir s’il est arrêté ou s’il en lecture.

La méthode « on_stop » est appelée à la fin du son.

 

Description

Le fichier audio doit être joué par le scheduler et non directement :

filename = "youi.mp3"
self.currentSound = SoundLoader.load(filename)
Clock.schedule_once(self.play_current_sound, 0)

De plus, d’autres méthodes sont définies.

1. Méthode pour lancer la lecture du fichier audio :

    def play_current_sound(self, arg):
        self.currentSound.play()
        self.currentSound.bind(on_stop = self.stop_sound)

Attention à respecter cet ordre sinon à la fin du son, la méthode « on_stop » n’est pas appelée.

 

2. Méthode qui arrête la lecture du fichier audio (forcée par un bouton) ou qui est appelée en fin de lecture :

    def stop_sound(self,  arg = None):
        self.currentSound.unbind(on_stop = self.stop_sound)
        if self.currentSound is not None:
            self.currentSound.stop()

 

Conclusion

Avec ce principe, la méthode « stop_sound » est appelée lorsque le fichier est terminé ou lorsque l’utilisateur a volontairement arrêté la lecture. Il est possible de présenter un bouton [Stop] uniquement lorsqu’il y a un son qui est joué.

Cette fonctionnalité de lecture d’un son est implémentée de manière différente en le PC (sous Linux) et un SmartPhone Android. Il faudra faire les ajustements au dernier moment ou implémenter une interface…

 

Kivy : scrolling horizontal

Introduction

Dans une page web, un tel composant s’appelle un carrousel. Il permet de faire passer les éléments de la droite vers la gauche ou inversement. Dans Kivy, ce widget n’existe pas en tant que tel, mais un composant plus générique est disponible. Reste à le configurer correctement.

source de l’image : http://www.androidpatterns.com/uap_pattern/carousel

Tout est dans le kv

Le « main.py » de l’application de démo est très classique :

import kivy
kivy.require('1.8.0')

from kivy.config import Config
Config.set('graphics', 'width', '450')
Config.set('graphics', 'height', '800')

__version__ = '0.1'

from kivy.app import App
from kivy.uix.screenmanager import Screen

class HScroll(Screen):
    pass

class HScrollApp(App):
    def build(self):
        return HScroll()

if __name__ == '__main__':
    HScrollApp().run()

 

Le fichier « hscroll.kv » défini les éléments de manière statique. Si besoin, il faudra les dynamiser dans votre application. Dans cet exemple, il y a un widget Label avec le texte « Horizontal scrolling » et un widget ScrollView qui définira la zone de scrolling.

<HScroll>:
    BoxLayout:
        orientation: 'vertical'
        canvas.before:
            Color:
                rgba: 1,1,1, 1
            Rectangle:
                pos: self.pos
                size: self.size

        Label:
            text: "Horizontal scrolling"
            size_hint_y: None
            height: sp(25)
            font_size: '16sp'
            color: (0,0,0, 1)
        ScrollView:
            GridLayout:
                rows: 1
                spacing: 10
                size_hint_x: None
                width: sp( 7 * (100 + 10) )

                canvas.before:
                    Color:
                        rgba: 0.2,0.2,0.2, 1
                    Rectangle:
                        pos: self.pos
                        size: self.size
                Image:
                    source: "images/disc-large.png"
                    size_hint_x: None
                    width: sp(100)
                Image:
                    source: "images/disc-medium.png"
                    size_hint_x: None
                    width: sp(100)
                Image:
                    source: "images/disc-small.png"
                    size_hint_x: None
                    width: sp(100)
                Image:
                    source: "images/ic_action_accept.png"
                    size_hint_x: None
                    width: sp(100)
                Image:
                    source: "images/ic_action_back.png"
                    size_hint_x: None
                    width: sp(100)
                Image:
                    source: "images/ic_action_cancel.png"
                    size_hint_x: None
                    width: sp(100)
                Image:
                    source: "images/ic_action_cloud.png"
                    size_hint_x: None
                    width: sp(100)

 

Tout le travail du ScrollView est de tronquer un widget à l’intérieur, un GridLayout, plus grand (trop grand) et nécessitant un scrolling pour le voir entièrement.

Pour ce faire, il faut que le GridLayout ait une largeur définie. Dans cet exemple, c’est en dur : il y a 7 images de 100sp chacune. Sa largeur sera donc de 7 * (100 + 10 de spacing).

Il faut donc :

  1. Un widget ScrollView qui contient un GridLayout
  2. Une seule ligne dans le GridLayout (« rows: 1″)
  3. Définir la largeur du GridLayout (« size_hint_x: None » et width: sp(…) »)

Avec ces informations, vous pourrez faire un joli scrolling horizontal. Pour un scrolling vertical, il suffit de mettre une colonne (point #2) et de changer les largeurs en hauteurs (point #3).

 

Kivy : accès à l’appareil photo sous Android

Introduction

Depuis une application Kivy, il est possible d’appeler des objets Android et les faire interagir en utilisant Python. Pour ce faire, une interface est disponible sous le nom de « jnius ». En effet, c’est très pratique et génial…

Voici un exemple d’utilisation de l’appareil photo, le tout réutilisable dans vos applications Kivy, car dans un module à part (genre « Service »).

 

Appel de l’appareil photo dans l’application Kivy

Attention, avant de procéder à l’appel de caméra pour prendre une photo, il faut savoir une chose : l’application Kivy se mets en pause. Pour ce faire, il est nécessaire que l’instance de App définisse la méthode « pause » et renvoie « True ». C’est un peu comme si Kivy disant « je mets en pause l’appli, est-ce que j’en ait le droit ? ». Si « True », je passe à la suite. Si « False », je stoppe.

 

Le service d’appel de la photo est défini comme ceci (basé sur le tuto « takepicture ») :

from jnius import autoclass, cast
from android import activity
from functools import partial
from kivy.clock import Clock
import os

Intent = autoclass('android.content.Intent')
PythonActivity = autoclass('org.renpy.android.PythonActivity')
MediaStore = autoclass('android.provider.MediaStore')
Uri = autoclass('android.net.Uri')
Environment = autoclass('android.os.Environment') 

class CameraService(object):
    '''
    Service pour acceder a la camera du device.
    Attention, seules les images JPG sont autorisées.
    '''

    def __init__(self, savingPath = None, aFilename = None):
        '''
        Constructeur
        savingPath doit se terminer par le separateur de répertoire
        '''
        if savingPath is None:
            savingPath = Environment.getExternalStorageDirectory().getPath()
        self.path = savingPath
        self.filename = aFilename
        self.picture = None

    def __define_new_filename(self):
        index = 0
        while True:
            index += 1
            fn = os.path.join(self.path, 'takepicture{}.jpg'.format(index))
            if not os.path.exists(fn):
                return fn

    def take_picture(self, aCallbackFunction):
        self.callbackFunction = aCallbackFunction
        if self.filename is None:
            self.filename = self.__define_new_filename()
        uri = Uri.parse('file://' + os.path.join(self.path, self.filename) )
        uri = cast('android.os.Parcelable', uri)

        activity.bind(on_activity_result=self.__on_activity_result)

        intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
        intent.putExtra(MediaStore.EXTRA_OUTPUT, uri)
        PythonActivity.mActivity.startActivityForResult(intent, 0x123)

    def __on_activity_result(self, requestCode, resultCode, intent):
        if requestCode == 0x123:
            Clock.schedule_once(partial(self.__add_picture, self.filename), 0)

    def __add_picture(self, fn, *args):
        self.picture = fn
        self.callbackFunction(self.picture)
        activity.unbind(on_activity_result=self.__on_activity_result)

 

Son appel se fait dans une instance de Screen, par exemple :

class myScreen(Screen):
    #...

    def btn_take_photo(self):
        camera = CameraService()
        camera.take_picture(self.set_picture)

    def set_picture(self, newPicture_name):
        self.logLabel.text = "Photo : %s" % str(newPicture_name)

Lorsqu’un bouton est cliqué, la méthode « btn_take_photo() » est appelée. Celle-ci fait appel au service (sans paramètre dans ce cas) et lance la prise de photo avec un paramètre : la méthode à appeler lorsque l’utilisateur aura terminé : « self.set_picture ».

Cette méthode prend un paramètre : le nom du fichier sauvegardé par l’appareil photo du smartphone. Attention, seul le format JPEG est autorisé…

 

Conclusion

Pour reprendre cette photo, je vous conseille de la réduire avec PIL pour éviter d’avoir une image en 4128 x 2322 pixels.

im = Image.open(filename)
width, height = im.size
im.thumbnail( (width/4,height/4) , Image.ANTIALIAS)
im.save(filename,quality=95)

Pill Chart : graphe en forme de pillule

Introduction

Suite à la création du sélecteur de couleur sous forme d’une petite barre, j’ai voulu faire un graphique simple sur une dimension. J’ai repris les mêmes bases que le plugin précédent pour celui-ci.

Description

La syntaxe en JS est assez simple, un « PillChart » est défini par :

  • le DIV qui sera utilisé pour afficher le plugin
  • les données à afficher, avec les clés suivantes :
    • « label » : libellé au survol de la souris
    • « value » : valeur à faire apparaître dans la case du graphe
    • « width » : largeur en pixel ou en pourcentage
    • « cssAttr » : texte à coller dans l’attribut CSS « style »
new PillChart({
            containerId: 'pill_1',
            data: [ {
                label: "moins de 10 ans",
                value: 20,
                width: '20px',
                cssAttr: "background-color: #69C8FF;"
              }, ...
           ],
            updateCallback: function(obj){alert("clicked on : "+obj.label );}
        }).draw();

 

Démo

La page de démo est disponible sur cette page : http://jc.specs.free.fr/pillChart/demo.html

 

Démo avec une légende (générée par le plugin) :

 

Plugin MultiSelect dans le générateur de code

Introduction

Dans le générateur de code (voir l’article qui le décrit), les fichiers classiques de l’entité sont générés, mais il en manque une partie. Lorsqu’on défini une liaison n-n d’une entité vers une autre, on utilise un « objet de liaison ». Cet objet se décrit par :

  • un identifiant qui est clé unique de l’objet de liaison
  • l’identifiant de l’objet A
  • l’identifiant de l’objet B

Grâce à cet objet, un objet A est relié à n objets B et inversement, un objet B est relié à n objets A.

Exemple : entité « Album de bande dessinée » et entité « Auteur » :

  • un album de BD peut avoir plusieurs auteurs
  • un auteur peut avoir fait plusieurs albums de BD

 

L’objectif est de générer automatiquement du code pour avoir un multiselect dans la création et d’édition d’une entité A ou B.

 

Application avec les BD et les auteurs

L’objet « LienAuteurAlbum » est décrit comme ceci :

  • identifiant du lien
  • identifiant de l’album
  • identifiant de l’auteur

Remarque : l’identifiant du lien permet de supprimer facilement et sereinement un enregistrement avec une seule information, en plus des fonctionnalités offertes avec les autres champs.

Après moult réflexion, j’ai ajouté des features sur chaque entité de liaison :

Ces modifications sont faites dans la génération du helper et du modèle. Deux nouvelles vues ont étés faire pour l’appel du plugin multiselect, sous forme d’un fragment (une vue qui s’appelle depuis une vue principale). Une vue pour la création et une vue pour la modification.

Par contre, il faut modifier manuellement les controller et les vues des entités (exemple : Album) pour utiliser cet objet de liaison.

Appel du code généré pour la vue en édition ou en création d’album

Lorsque je crée un album, je veux avoir le multiselect des auteurs. Il faut alors ajouter dans la vue de création d’un album le code suivant :

<?php 

$data["multiselectOf"] = 'Auteur';
$this->load->view('lienauteuralbum/createMultiselectLienAuteurAlbumFragment_view.php', $data);

?>

Le fichier « lienauteuralbum/createMultiselectLienAuteurAlbumFragment_view.php » est généré et ne doit pas être modifié. Dans le paramètre « multiselectOf », il faut spéficier que je veux un multiselect de … « Auteur », afin d’avoir une liste d’auteurs à choisir.

Idem dans la vue d’édition d’un Album.

 

Modification du controller d’édition d’album

Dans le controller d’édition, lorsqu’on sauve les données saisies, il faut [1] supprimer les anciennes relations dans « LienAuteurAlbum » dans lesuqelles il y a l’album édité et [2] créer des nouveaux objets « LienAuteurAlbum » avec cet album et les auteurs cochés.

Première partie :

$lienAuteurAlbum_autidaut = $this->input->post('LienAuteurAlbum_autidaut');
LienAuteurAlbum_model::deleteAllLienAuteurAlbumsBy_xaaidalb($this->db, $model->albidalb);

ligne 1 : récupération des liens avec des identifiants d’auteurs que l’utilisateur a coché dans le multiselect

ligne 2 : suppression des anciens liens à partir de l’identifiant de l’album

 

Deuxième partie :

foreach($lienAuteurAlbum_autidaut as $autidaut){
            $auteurModel = new LienAuteurAlbum_model();
            $auteurModel->xaaidalb = $model->albidalb;
            $auteurModel->xaaidaut = $autidaut;
            $auteurModel->save($this->db);
}

Pour chaque auteur coché, on créer un nouveau lien avec :

  • album : celui que je suis en train d’éditer
  • auteur : un auteur coché

Remarque : Dans le controller de création d’un album, la première partie destinée à supprimer les anciens liens avec les auteurs n’est pas à faire.

 

 Sans oublier le javascript

Le plugin JS est à charger après les différents appels de jQuery et du JS du plugin multiselect. J’ai choisi de le mettre manuellement dans le JS de la vue de l’objet édité ou créé.

$(function(){
    $("#LienAuteurAlbum_autidaut").multiselect();
});

 

Résultat

Dans cet exemple, le champ « Genre » est un select simple (déjà disponible dans le générateur). Le champ « Liste des auteurs » fait appel au plugin multiselect.

 

Conclusion

Finalement, la génération de code a ses limites, mais beaucoup de choses sont pré-mâchées. Pour aider les futurs développeurs à intégrer le plugin, j’ai inséré le code à copier-coller dans la vue et dans le JS.

Rien n’est généré pour le controller, il faudra lire cet article si vous ne savez pas comment faire (mais je trouve que ce n’est pas trop difficile).