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

Réclame : Protection écran de smartphone

Après avoir prêté mon smartphone aux collègues, j’ai du enlever 5ml d’huile de coude sur la vitre pour m’en servir. La surface s’est comporté comme une véritable éponge à saleté.

J’ai alors testé une protection vendue par docphone.org pendant mes vacances d’été. Même la crème solaire et le sable n’y sont pas venus à bout. Expérience concluante. J’attends mes collègues pour l’ultime test « prends un autre croissant et va taxer le Samsung de Julien ».

GanttCalendar + VectorManager

Utilisation de VectorManager

VectorManager est une bibliothèque JS destinée à gérer les vecteurs au sens mathématique (développée par l’auteur de cet article). Avec le GanttCalendar, il est possible de faire calculer à cette bibliothèque JS une information sur l’occupation des ressources d’un calendrier. Le GanttCalendar a donc été mis à jour avec VectorManager pour ajouter cette information pertinente : « au moins une ressource est occupée » et « toutes les ressources sont occupées ».

Pour ces 2 informations, 2 barres sont ajoutées dans le groupe de ressources :

  • bleu : « au moins une ressource est occupée »
  • rouge : « toutes les ressources sont occupées »

 

Bonus : Cette représentation supporte le zoom sur les calendriers sans ajouter de code spécifique. C’est l’avantage de rester sur la notion mathématique le plus loin possible dans le code, jusqu’au moment de l’affichage.

 

Exemples

Calendrier hebdomadaire :

Zoom sur le calendrier mensuel :

 

Démo

Les liens suivants permettent de voir le résultat en live :

Ces pages ont été testées avec FireFox. Il est fort probable qu’Internet Explorer n’affiche pas correctement les calendriers.

 

Sources

https://github.com/jchome/ganttCalendar

Impressive : des slideshow en python

A propos d’impressive

Le logiciel « impressive » est écrit en python et permet de faire des slideshow en boucle avec un délai entre les slides et des effets de transition. C’est un peu comme MS PowerPoint, mais il n’y a pas de gestion d’effet de texte (qui arrive au fur et à mesure, par exemple).

En plus, le paramétrage se fait par script ou fichier de configuration. Il est donc réservé à des utilisateurs avertis, avec des notions de programmation.

Le programme accepte différents paramètres en entrée :

 

Exemple d’usage basique

Lors d’un tournoi de sport, un écran est disponible au public et plusieurs images sont présentées. Ces images tournent en boucle ; certaines sont affichées plus longtemps que d’autres. Vous allez utiliser Impressive avec un script pré-paramétré qui détaillera les images à présenter (l’ordre d’apparition, la durée d’affichage, l’effet de transition vers le slide suivant).

Exemple de script :

# -*- coding: iso-8859-1 -*-
PageProps = {
  1: {
    '_file': "01-presentation-01.png",
    'timeout': 10000,
    'transition': Crossfade
     },
  2: {
    '_file': "01-presentation-02.png",
    'timeout': 10000,
    'transition': SlideLeft
     },
  3: {
    '_file': "01-presentation-03.png",
    'timeout': 30000,
    'transition': WipeDown
     },
  4: {
    '_file': "02-deroulement-01.png",
    'timeout': 30000,
    'transition': ZoomOutIn
     },
  5: {
    '_file': "03-lots-01.png",
    'timeout': 10000,
    'transition': SlideLeft
     }
}

Syntaxe :

  • ‘_file’ : le nom de l’image à afficher
  • ‘timeout’ : durée d’affichage en millisecondes
  • ‘transition’ : effet de transition vers le slide suivant

Pour quitter le slideshow avec un code retour particulier, il faut utiliser la syntaxe suivante :

{
...
  11: {
    '_file': "06-club-02.png",
    'timeout': 10000,
    'OnLeave': lambda: Quit(10)
     }
}

 

Le programme sera lancé avec ce script, selon la syntaxe suivante :

impressive --wrap --nologo --script 01-accueil.info *.png

Le slideshow sera affiché en boucle, en prenant toutes les images du répertoire. Seules les images du script seront utilisées.

 

Aller plus loin

Avec cette syntaxe, le programme couvre beaucoup de cas. Mais imaginons que les équipes du tournoi sont classée (avec un système de points) et qu’il serait intéressant de présenter des slides avec le classement de chaque équipe.

Pour ce faire, je vous propose un script en python qui, à partir d’une image de fond, va lire un fichier et écrire du texte pour générer l’image finale à intégrer dans une présentation.

image de fond

L’image dispose d’un cadre blanc pour écrire du texte par dessus.

 

Fichier du classement

# classement général 1/4
1 | Equipe A | 25 pts
2 | Equipe B | 20 pts
3 | Equipe C | 19 pts
4 | Equipe D | 15 pts
5 | Equipe E | 10 pts
6 | Equipe F | 9 pts

Le fichier défini 3 colonnes et présente 6 équipes sur les 24 en lisse. Il y aura donc 4 slides à faire pour le classement complet.

 

Image finale

Le texte est écrit avec la même police que celle utilise pour l’image de fond.

 

Script Python

Le script utilise PIL pour lire l’image, écrire le texte avec la bonne police et sauver l’image finale.

#!/usr/bin/env python

from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
import sys

background = sys.argv[1]
textfile = sys.argv[2]
outfile = sys.argv[3]

# top left starting position
position = (260,260)
color = (0,0,0)
fontsize = 72

img = Image.open(background)
draw = ImageDraw.Draw(img)
font = ImageFont.truetype("GROBOLD.ttf", fontsize)

resultsFile = open(textfile, 'r')
for line in resultsFile:
    if len(line.strip()) == 0:
        continue
    if line.strip()[0] == "#":
        continue
    fragments = line.split('|')
    if len(fragments) < 2:
        continue
    rang = fragments[0].strip()
    equipe = fragments[1].strip()
    score = fragments[2].strip()
    draw.text(position, rang, color, font=font)
    draw.text((position[0]+200, position[1]), equipe, color, font=font)
    draw.text( (position[0]+1000, position[1]), score, color, font=font)
    position = (position[0], position[1]+(fontsize*1.75) )
img.save(outfile)

Ce petit script Python prend 3 paramètres :

  • l’image de fond
  • le fichier texte
  • l’image à sauvegarder

Il faut préciser que les images font 1920×1080 pixels, d’où les positions d’écriture des textes.

OpenCV pour mesurer la qualité des images

Introduction

J’ai passé une autre soirée astro pour prendre des photos de Jupiter. Environ 220 clichés de plus ou moins bonne qualité, en fonction de l’atmosphère sont à trier. En général, certaines photos sont trop floues.

Comment faire le tri entre toutes ces photos pour ne garder que les plus nettes ? OpenCV va vous sauver la vie !

 

Principe

L’objectif est d’avoir un chiffre « qualité du focus », calculé sur chaque photo qui me permettra de trier mes 220 photos pour n’en sortir que les 100 plus nettes.

Pour ce faire, la technique la plus en vogue sur le net (stack overflow + publications) et de faire 2 filtres Sobel sur l’image, calculer les pics de gradients et faire une somme globale. Plus la somme est élevée, plus il y a de netteté.

 

Rappel / info à savoir

Sobel est un des filtres de détection des bords les plus connus (expliqué à la fac, dans des TP, etc). On peut citer aussi le Laplacien dans cette famille de filtres. Depuis une image en couleurs, ces filtres vont faire une image en dégradé de gris, avec :

  • en noir : les régions où il n’y a pas de bord (aplat de couleur ou changement doux)
  • en blanc : changement brutal de couleur (= un bord)

Ce qui est pratique avec Sobel, c’est qu’on peut lui dire dans quel axe il doit détecter les bords. Pour nous, ce sera un dans l’axe X et un dans l’axe Y de chaque image. Les pics de gradients se feront sur ces 2 axes pour avoir une large gamme de détection.

 

OpenCV

Ce n’est pas le premier script Python posté sur mon site qui utilise OpenCV. Pour ceux qui ne le savaient pas, Python a été créé à l’époque (au siècle dernier) pour interfacer du C/C++, donc faire des appels de fonctions programmées en C et compilées sur le PC. Ceci avec un langage plus simple que le C : le Python !

Bref, OpenCV propose une tonne de fonctions pour manipuler les images. Toutes, très éprouvées, très optimisées, et très math… (désolé pour les non-matheux)

 

Le script

Voici le script Python qui prend en entrée une liste d’images et sort un tri par netteté.

#!/usr/bin/python

import cv2, sys
import operator

files = sys.argv[1:]
all_ratio = {}
for filename in files:
    orig = cv2.imread(filename)

    sobel_dx = cv2.Sobel(orig, cv2.CV_64F, 1, 0, ksize=5)
    sobel_dy = cv2.Sobel(orig, cv2.CV_64F, 0, 1, ksize=5)
    magnitude_image = cv2.magnitude(sobel_dx,sobel_dy,sobel_dx);
    mag, ang = cv2.cartToPolar(sobel_dx, sobel_dy, magnitude_image) 

    ratio = cv2.sumElems(mag[0])
    all_ratio[filename] = ratio[0]

sorted_ratio = sorted(all_ratio.items(), key=operator.itemgetter(1))
index = 1
print(" Rang | Fichier      | Valeur calculee")
print("------|--------------|----------------")
for (filename, ratio) in reversed(sorted_ratio):
    print(" %04d | %s | %d" % (index, filename, ratio))
    index += 1

 

Exemple

Voici les 3 images utilisées dans l’appel du script (cliquez pour voir la photo originale) :

  • Image « apero girl.png », originale :
  • image « apero girl-5.png », copie de l’image originale, avec un flou gaussien de 5px :
  • image « apero girl-20.png », copie de l’image originale, avec un flou gaussien de 20px :

Résultat du script :

 Rang | Fichier      | Valeur calculee
------|--------------|----------------
 0001 | apero girl.png | 154324
 0002 | apero girl-5.png | 146422
 0003 | apero girl-20.png | 142630

Conclusion : l’image « apero girl.png » est le plus net.

 

Remarque

En regardant le script « dans le blanc des yeux », on serait tenté de dire ceci : oui, mais ton script ne te donne de bons résultats uniquement sur des photos proches, d’une prise de vue d’un même objet, qui serait plus ou moins flou.

Et bien, en effet, si on prend 2 photos qui n’ont rien à voir, les indices calculés seront incohérents et le tri par « qualité de netteté » ne voudra rien dire.

Donc, restez vigilant à ne passer dans ce script que des photos d’un même objet, dont la netteté varie.

 

Astrophoto : Découpage autour d’une planète

Introduction

Jupiter est bien visible ce soir ; il n’y a pas trop de nuages et la lune est loin. Je sort rapidement le matériel et prend environ 140 photos plus ou moins floues de la planète, en espérant pouvoir en tirer quelque chose avec des scripts. Je ne mets pas d’oculaire pour grossir l’image, pour voir ce que je peux tirer de ce premier essai. La mise au point se fait tant bien que mal dans le viseur de l’appareil photo.

 

Clichés bruts

Les clichés représentent sur un fond noir la planète, sans ses satellites. Si la pose est plus longue, Jupiter apparaît comme un disque blanc indistinct. Je prend donc plusieurs séries de poses, en 1600 ISO, en 1/500 sec ; et en 800 ISO, en 1/320 sec ; en mode rafale de 10 poses.

Le problème est que selon la série de clichés, Jupiter n’est pas à la même place. Grâce à un script Python, en utilisant la bibliothèque OpenCV, il est possible de :

  • trouver où est la planète dans l’image : sa position
  • déterminer sa taille en pixels : le centre
  • faire un découpage de la zone (10x plus gros que la taille de la planète)

 

Voici le script :

#!/usr/bin/python

import cv, sys
import numpy as np

# grab image
files = sys.argv[1:]
filename = files[0]
filename_base = filename[:filename.rfind(".")]
orig = cv.LoadImage(filename)
orig_clean = cv.LoadImage(filename)

# create tmp images
grey_scale = cv.CreateImage(cv.GetSize(orig), 8, 1)
processed = cv.CreateImage(cv.GetSize(orig), 8, 1)

cv.Smooth(orig, orig, cv.CV_GAUSSIAN, 3, 3)

cv.CvtColor(orig, grey_scale, cv.CV_RGB2GRAY)

# do some processing on the grey scale image
cv.Erode(grey_scale, processed, None, 10)
cv.Dilate(processed, processed, None, 10)
cv.Canny(processed, processed, 5, 70, 3)
cv.Smooth(processed, processed, cv.CV_GAUSSIAN, 15, 15)

storage = cv.CreateMemStorage(0)

contours = cv.FindContours(processed, storage, cv.CV_RETR_EXTERNAL)
# N.B. 'processed' image is modified by this!

#contours = cv.ApproxPoly (contours, storage, cv.CV_POLY_APPROX_DP, 3, 1)
# If you wanted to reduce the number of points...

cv.DrawContours (orig, contours, cv.RGB(0,255,0), cv.RGB(255,0,0), 2, 3, cv.CV_AA, (0, 0)) 

def contour_iterator(contour):
  while contour:
    yield contour
    contour = contour.h_next()

spot_index = 0
for c in contour_iterator(contours):
  # Number of points must be more than or equal to 6 for cv.FitEllipse2
  if len(c) >= 6:
    # Copy the contour into an array of (x,y)s
    PointArray2D32f = cv.CreateMat(1, len(c), cv.CV_32FC2)

    for (i, (x, y)) in enumerate(c):
      PointArray2D32f[0, i] = (x, y)

    # Fits ellipse to current contour.
    (center, size, angle) = cv.FitEllipse2(PointArray2D32f)

    # Convert ellipse data from float to integer representation.
    center = (cv.Round(center[0]), cv.Round(center[1]))
    #print("Center : %dx%d" % center)
    size = (cv.Round(size[0] * 0.5), cv.Round(size[1] * 0.5))
    #print("Size : %dx%d" % size)
    (w,h) = (size[0]*10 , size[1]*10)
    (x,y) = (center[0]-w/2 , center[1]-h/2)
    im_crop = orig_clean[y: y + h, x: x + w]
    cv.SaveImage("%s-%02d.jpg" % (filename_base, spot_index) , im_crop)
    spot_index += 1

Il est inspiré de cette page : http://stackoverflow.com/questions/9860667/writing-robust-color-and-size-invariant-circle-detection-with-opencv-based-on

Image originale fait 4272×2848 pixels (retaillée en 800×533 pour l’insérer dans l’article) :

Voici une extraction par le script (la résolution est conservée) :

L’image est beaucoup plus petite, sans perdre la qualité de la portion qui m’intéresse.

Sur les 140 photos, je ne retient que 19 de bonne qualité. Les autres sont foules ou déformées. Les photos ainsi triées doivent être retaillées pour avoir la même taille et procéder à une fusion (un script imagemagick permet de retailler une image avec un bord noir).

Avec des scripts déjà utilisés dans un article précédent, voici le résultat de cette première expérience (image tournée à 90° pour voir les bandes horizontales) :

 

La suite ?

Sans agrandissement (autre que celui du télescope), la planète reste petite. Je pense qu’il ne sera pas possible d’exploiter mieux les photos pour tirer de meilleurs résultats.

La prochaine fois, je ferai le même procédé avec un grossissement plus fort et une mise au point manuelle optimum.

 

Scripts python pour modifier des images

Besoin : retouche de photo pour l’astronomie

Pendant une nuit de Janvier, la comète LoveJoy est passée dans notre ciel. Ne sachant où regarder et où pointer mon appareil photo, j’ai pris 8 clichés de 30 secondes d’une portion du ciel, avec beaucoup de pollution lumineuse.

Exemple d’image prise (3175 x 1881 px) :

 

Scripts

Après plusieurs essais avec TheGimp, voici les opérations faites sur les images :

  • Analyse visuelle : sur le lot, une image a tracé le passage d’un avion avec ses lumières clignotantes. ça en fait une de moins à traiter.

 

  • Alignement des 7 images, avec un programme spécial. Voici la ligne de commande (le fichier final n’est pas utile, mais les images alignées qui ont servies sont à garder) :
align_image_stack -a aligned -C *.JPG -o merged -v

Les images sont alignées et nommées « aligned0001.jpg » à « aligned0008.jpg ».

 

  • Empilement en mode « fusion » des 7 images. Les images étant assez claires (à cause de la pollution lumineuse), j’ai opté pour une fusion pour compenser ce qui ne serait pas dans une image…

Script :

#!/usr/bin/python

from PIL import ImageChops
import os, Image, glob
import sys

files = sys.argv[1:]
print("> Lighter...")
finalimage=Image.open("./"+files[0])
for i in range(1,len(files)):
    currentimage=Image.open("./"+files[i])
    print("  Processing : %s" % files[i])
    finalimage=ImageChops.lighter(finalimage, currentimage)

outFile = "all-lighter-br.jpg"
print("  Saving : %s" % outFile)
finalimage.save(outFile,"JPEG")

L’image composite est relativement proche d’une image de départ.

 

  • Création d’une image floue. L’objectif est de supprimer la pollution lumineuse. Je suis sont donc parti d’une image proche du fond clair (= l’image floue) pour l’enlever à l’image composite. Les différents paramètres d’ImageMagick ne m’ont pas convaincu, je suis alors passé par un autre script python (avec un rayon de flou à 30 px).
#!/usr/bin/python

import sys
import ImageFilter
from PIL import Image

class MyGaussianBlur(ImageFilter.Filter):
    name = "GaussianBlur"
    def __init__(self, radius=2):
        self.radius = radius

    def filter(self, image):
        return image.gaussian_blur(self.radius) 

inputFile = sys.argv[1]
outputFile = sys.argv[2]
print("> Blur...")
im = Image.open(inputFile)
im1 = im.filter(MyGaussianBlur(radius=30))
print("  Saving : %s" % outputFile)
im1.save(outputFile)

 

  • Soustraction de l’image floue à l’image composite. Là aussi, un script python propose cette fonctionnalité :
#!/usr/bin/python

import sys
from PIL import Image, ImageChops

inputFile_1 = sys.argv[1]
inputFile_2 = sys.argv[2]
outputFile = sys.argv[3]

print("> Substraction...")
im1 = Image.open(inputFile_1)
im2 = Image.open(inputFile_2)
out = ImageChops.subtract(im1,im2,1,0)
print("  Saving : %s" % outputFile)
out.save(outputFile)

 

  • Le résultat manque de peps et reste plutôt terne. Avec TheGimp, j’ai vu que le filtre « screen » d’une image sur elle-même éclaircit les étoiles et garde le fond sombre. Voici le script python pour le faire en une ligne de commande :
#!/usr/bin/python

import sys
from PIL import Image, ImageChops

inputFile_1 = sys.argv[1]
outputFile = sys.argv[2]

print("> Screen...")
im1 = Image.open(inputFile_1)
out = ImageChops.screen(im1,im1)
print("  Saving : %s" % outputFile)
out.save(outputFile)

 

Image finale

La comète est visible, oui, mais c’est un point bleuté parmi les étoiles de la constellation du taureau. J’ai déjà eu de la chance de pointer mon appareil photo vers l’endroit où elle se trouve.

 

Autre version

Voici un autre processus, qui donne de meilleurs résultats :

  • Alignement (comme avant)
  • Flou sur chaque image + suppression pour enlever la pollution lumineuse
  • Ajout de chaque image l’une sur l’autre, avec le script suivant :
#!/usr/bin/python

import os, Image, sys
import numpy as np

files = sys.argv[1:]
print("> Clip...")
image=Image.open("./"+files[0])
im=np.array(image,dtype=np.float32)
for i in range(1,len(files)):
    currentimage=Image.open("./"+files[i])
    im += np.array(currentimage, dtype=np.float32)
im /= len(files) * 0.25 # lowered brightness, with magic factor
# clip, convert back to uint8:
final_image = Image.fromarray(np.uint8(im.clip(0,255)))
final_image.save('all-clip.jpg', 'JPEG')

 

Le résultat est plus évident, mais la comète reste discrète :

Comparatif ionic / Kivy – match retour

Introduction

Suite à l’article visant à comparer ionic et Kyvi, je suis allé plus loin dans la création d’une application mobile avec ionic (ben oui, je lui ai donné une bonne note, alors j’ai creusé !). Finalement, je vais pouvoir appuyer mes propos sur des exemples concrets, en faisant une deuxième copie d’une application Kivy, mais avec ionic (écrans proches et fonctionnalités similaires).

Pour rappel, le framework ionic part d’une bonne base : utiliser les connaissances d’un développeur web pour faire des applications mobiles. Il s’avère que (à mon avis) Kivy s’en sort mieux, même avec ses points faibles. Voici les notes du match retour, sur 20.

 

Comparatif de l’installation

Notes du match aller : Kivy 18/20 ; ionic : 15/20

Après plusieurs applications créées avec ionic, ces notes ne changent pas. La documentation des débutants servira pour les prochains développements. Je trouve relativement dommage que les fichiers de configuration des plates-formes soient avec le projet. On n’est plus dans le crossplatform (ou « platform independant ») dès le début, mais on va s’arranger avec les plates-formes cibles.

Verdict : Kivy 18/20 ; ionic : 15/20

 

Editeur de code (ou IDE)

Notes du match aller : 18/20 pour les deux

Même avec Eclipse et des plugins (JS, HTML, CSS), je n’ai pas réussi à configurer rapidement l’IDE sur un projet ionic pour me faire gagner plus de temps qu’un simple éditeur de texte avec coloration syntaxique (genre Notepad++). Les recherches de dépendances (F3, CRTL-Clic) ne fonctionnent pas, c’est la cata. Heureusement que les applications sont simples.

Avec Eclipse, les classes Phyton sont reconnues et facilement retrouvées. Il ne manque que l’interprétation du langage kv pour avoir un 20/20.

Verdict : Kivy 18/20 ; ionic : 10/20

 

Documentation et exemples

Notes du match aller : 18/20 pour les deux

Après quelques écueils, je doit reconnaître que la documentation ionic (cordova et ses copains) ne sont pas si facilement applicables. Des exemples et explications sont disponibles, mais il faut mettre la frontale. De longues recherches et tests sont nécessaires pour investiguer sur un terrain inconnu. Gare aux culs de sac.

Verdict : Kivy 18/20 ; ionic : 15/20

 

Le premier « Hello world »

Notes du match aller : Kivy 18/20 ; ionic : 15/20

Attention, si on part avec de mauvaises bases et structuration du code, ca devient vite un gros bordel. Le framework ne cadre pas du tout les composants (Services, Factory, Modèles, etc) : tout peut être dans un seul fichier de 600 ko de code. Pour débuter avec une première application, ces bases sont presque obligatoires (par exemple avec CodeIgniter en PHP, qui est un régal à comparer d’ionic). Je trouve dommage que les exemples « clé en main » soient dépourvus de cette structuration.

Kivy propose plus de contrainte, car il se base sur Python qui défini déjà des packages et des modules (ce qui doit aider grandement Eclipse à retrouver les sources des classes).

Verdict : Kivy 18/20 ; ionic : 12/20

 

Développement d’une application sur un PC

Notes du match aller : Kivy 18/20 ; ionic : 12/20

Pour ce cas, je pense avoir plutôt bien jugé mes notes. Le développement est relativement laborieux avec ionic. Heureusement qu’avec un plugin pour Chrome, on arrive à avoir un rendu potable et avancer dans la création des applications. Les logs sont dans la console Javascript du navigateur, mais il n’y a pas tout. Comparé à Kivy, où tout sort dans la console d’Eclipse, c’est bien moins confortable.

Verdict : Kivy 18/20 ; ionic : 12/20

 

Déploiement sur mobile (Android)

Notes du match aller : 18/20 pour les deux

Avec ionic, on peut profiter du live-reload pour pallier aux problèmes d’utilisation des ressources présentes uniquement sur le device (ex: Base de données SQLite). Le déploiement est facile pour les deux frameworks, même avec une structuration complexe et des fichiers divers.

Verdict : 18/20 pour les deux

 

Développements sans appels au device

Notes du match aller : 18/20 pour les deux

Les développements ionic nécessitent une « boite » simulant le mobile. C’est faisable avec un navigateur comme Chromium et un plugin ;  c’est un peu bricolé. Avec Kivy, c’est une vraie fenêtre de l’OS qui s’ouvre.

Verdict : Kivy 18/20 ; ionic : 15/20

 

Appels aux fonctionnalités du device

Notes du match aller : Kivy 12/20 ; ionic : 18/20

Le désenchantement d’ionic continue lors de développements faisant appel au fonctionnalités du device (GPS, Base de données, caméra, etc.). Il n’est plus possible de se passer d’un device et de tout tester sur le mobile. On arrive aux limites de la magie du crossplatform. Il en fallait bien quelques unes, ce n’est pas du code natif et il n’y a pas de façade pour s’en abstraire (ca commence à arriver sur Kivy).

Verdict : Kivy 12/20 ; ionic : 12/20

 

Design et interface

Notes du match aller : Kivy 4/20 ; ionic : 18/20

Sauf nouveauté coté Kivy, ionic reste très pratique pour les interfaces nécessitant un design très travaillé.

Avec des widgets prédéfinis et réutilisables (et paramétrables), Kivy sort quand même la tête hors de l’eau, sans avoir de solution miracle. Il faut donc prévoir de faire des interfaces simples avec Kivy…

Verdict : Kivy 8/20 ; ionic : 18/20

 

Conclusion

Voici un rappel des notes obtenu par nos deux coureurs :

  • Installation : Kivy 18/20 ; ionic : 15/20
  • IDE : Kivy 18/20 ; ionic : 10/20
  • Documentation : Kivy 18/20 ; ionic : 15/20
  • Hello World : Kivy : 18/20 ; ionic : 12/20
  • Développement sur PC : Kivy 18/20 ; ionic : 12/20
  • Déploiement sur mobile : 18/20 pour les deux
  • Développements sans appels aux device : Kivy 18/20 ; ionic : 15/20
  • Appels aux fonctionnalités du device : Kivy 12/20 ; ionic : 12/20
  • Design et interface : Kivy 8/20 ; ionic : 18/20

Total (après utilisation plus approfondie) : Kivy 146 pts (+4 pts) ; ionic : 127 pts (-23pts)

Pour ma prochaine application mobile, si l’interface doit être simple, elle sera en Kivy. Si elle doit avoir une interface plus travaillée, ce sera avec ionic, mais elle ne sera pas trop complexe (fonctions simples).

 

SoundBox avec ionic

Introduction

Après avoir réalisé une boite à sons avec Kivy, j’ai tenté de faire la même chose avec ionic. Les réflexes sont les mêmes, la méthode de travail est un peu différente.

 

Ajout du plugin Media

Pour faire jouer un son sur le mobile, il est nécessaire d’installer le plugin Cordova « Media ». Malheureusement, il ne fonctionne pas sous le navigateur du développeur. Il faudra faire le test sur le device mobile. Seule différence notable avec Kivy, qui permet de jouer un son presque de la même façon depuis le PC ou depuis le mobile.

 

Structuration du code

Même s’il n’y a pas grand chose, je tiens à avoir le code le plus structuré et le plus propre possible. L’application se découpe en 2 pages : la liste des catégories (page #1) et la liste des sons de la catégorie choisie (page #2).

Je me suis fortement inspiré du code de cette application : https://github.com/angular-app/angular-app/tree/master/client/src/app

Voici le contenu du répertoire www :

  • app : Répertoire de l’application
    • intro : Répertoire des fichiers de la vue « intro », page #1 de l’application
      • intro.js : Controller de la page « intro »
      • intro.tpl.html : Vue de la page « intro »
    • sounds : Répertoire de la page #2
      • sounds.js : Controller de la page des sons
      • sounds.tpl.html : Vue de la page des sons
    • app.js : Définition des services et du routage des pages
  • css : Répertoire vide pour les CSS spécifiques à l’application
  • js : Répertoire vide pour bibliothèques JS spécifiques à l’application
  • lib : Répertoire des plugins ajoutés pour l’application (géré par cordova)
  • mp3 : les sons

 

Détail du code

Il y a 6 fichiers importants pour que l’application fonctionne correctement.

index.html

<body ng-app="starter">
    <ion-nav-view></ion-nav-view>

    <script src="lib/media/Media.js"></script>
    <script src="lib/media/MediaError.js"></script>

    <script src="app/app.js"></script>

    <script src="app/intro/intro.js"></script>
    <script src="app/sounds/sounds.js"></script>
</body>

 

app.js

angular.module('soundBox.services', [])
.factory('CategoryService', function() {
    // Might use a resource here that returns a JSON array
    var categories = {
            "Animaux": ["Aigle", "Cigales"],
            "Divers": ["Claque", "Dentiste", "Klaxon", "PopDing", "Pouic", "Sieste bebe"],
            "Humain": ["Applaudissements", "Ramirez - alors ausweis papier svp - au trot", "Ramirez - hop hop hop hop", "Ramirez - jai dit ausweis"],
            "Objets": ["Canette Coca", "Casse assiette", "Cloche hotel", "Guitare", "Matrix_phone", "Telephone_Ancien", "Toy telephone", "Corne de brume", "Jouet de chien", "Sirene Alarme", "Telephone_x2"],
            "TV": ["Thames TV", "The Benny Hill Show"]
        };
    return {
        all: function() {
            return Object.keys(categories);
        },
        get: function(aCategory) {
            return categories[aCategory];
        }
    };
})
;

var app = angular.module('starter', ['ionic', 'ui.router', 'soundBox.services'])

.config(function($stateProvider, $urlRouterProvider) {

  $stateProvider.state('intro', {
    url: '/',
    templateUrl: 'app/intro/intro.tpl.html',
    controller: 'IntroCtrl'
  });

  $stateProvider.state('sounds', {
    url: '/sounds/:category',
    templateUrl: 'app/sounds/sounds.tpl.html',
    controller: 'SoundsCtrl'
  });

  $urlRouterProvider.otherwise('/');

});

Ce fichier aurait pu être découpé en 2 : le service et le routage.

 

intro.tpl.html

<ion-view title="Sound Box">
    <ion-nav-bar type="bar-positive"
        animation="nav-title-slide-ios7"
        back-button-type="button-icon button-clear"
        back-button-icon="ion-ios7-arrow-back">
        <ion-nav-back-button>
            <i></i> Back
        </ion-nav-back-button>
    </ion-nav-bar>

    <ion-content>
        <ion-list>
            <ion-item ng-repeat="item in items" href="#/sounds/{{item}}">
                {{item}}
                <i></i>
            </ion-item>
        </ion-list>
    </ion-content>
</ion-view>

 

intro.js

app.controller('IntroCtrl', function($scope, $location, $state, CategoryService) {
    $scope.items = CategoryService.all();
})
;

 

sounds.tpl.html

<ion-view title="Sound Box">
    <ion-nav-bar type="bar-positive"
        animation="nav-title-slide-ios7"
        back-button-type="button-icon button-clear"
        back-button-icon="ion-ios7-arrow-back">
        <ion-nav-back-button>
            <i></i> Back
        </ion-nav-back-button>
    </ion-nav-bar>

    <ion-content>
        <ion-list>
            <ion-item ng-repeat="item in items" ng-click="selectSound('{{item}}')">
                {{item}}
            </ion-item>
        </ion-list>
    </ion-content>
</ion-view>

 

sounds.js

app.controller('SoundsCtrl', function($scope, $state, $location, $stateParams, $ionicPlatform, CategoryService) {

    var sounds = CategoryService.get($stateParams.category);
    $scope.items = sounds;

    $scope.selectSound = function(sound) {
        var src = "/mp3/"+sound+".mp3";
        if(ionic.Platform.isAndroid()){
            src = "/android_asset/www" + src;
        }
        if($scope.media){
            $scope.media.stop();
        }
        $scope.media = new Media(src, function(){console.log("successfuly played "+ sound);}, function(e){console.log(e);});
        $scope.media.play();
        // ne pas faire d'autres interprétations du click
        return false;
    };

    $ionicPlatform.onHardwareBackButton(function(){
        if($scope.media){
            $scope.media.stop();
            $scope.media = null;
        }
    });
});

 

Conclusion / comparaison avec Kivy

Le code doit être rigoureusement écrit. Par exemple le tag « <ion-view> » doit être le seul dans la vue, sinon l’affichage est un peu cassé.

Selon le formalisme utilisé, les fichiers sont multiples mais courts et bien classés, ce qui est très agréable à maintenir.

Le design n’a pas été fait de manière aussi complète que l’application réalisée avec Kivy, mais j’imagine que les CSS et images seront faciles à manipuler, comme pour un site web.

Vivement d’autres applications pour mieux comparer les frameworks dans une application plus complète.

 

Téléchargement de l’APK

SoundBox-ionic-debug.apk