dimanche 19 janvier 2020

#1 Roadbook Unity - Les furtifs V0.1

C'est parti dans l'apprentissage de la création d'un jeu vidéo avec Unity. Pourquoi Unity ? Car malgré sa complexité, c'est la plateforme de développement qui me donnera le plus d'outil pour mettre en pratique mes idées sur le papier. Multi-plateforme, j'ai besoin de faire des exports autant pour windows, android qu'en html5. Je ne veux pas me limiter à un seul type de jeu comme le permettent des moteurs tels que RGSS (RPG Maker) ou Adventure Game Studio. Pourquoi pas Construct ou Game Maker ? Car il faut faire un choix. Je suis, de plus, peu intéressé par la création de sprites car faire du pixel art n'est pas dans mes cordes. Pour une première mise en jambe, l'objectif sera de réfléchir à un gameplay sous toutes ses coutures. Son développement permettra d'étayer ou infirmer les hypothèses choisies.


Comme base de travail, j'ai choisi l'univers du dernier ouvrage d'Alain Damasio : les Furtifs. Je souhaite que le joueur incarne une de ses chimères qui circulent -véloces- dans les angles morts de la vision humaine. Dès qu'elles sortent de cette périphérie, bref, quand elles sont vus, elles meurent. Voila déjà notre gameplay de base : le joueur doit se déplacer en évitant d'être vu. On est donc sur un classique du jeu d'infiltration façon Metal Gear Solid. Le composant qui permet ce mécanisme est le Field Of View (FOV). On règle un angle et une distance de vue pour l'adversaire. Si le joueur est dans la zone correspondante, il a perdu !

Ce gameplay, je veux l'enrichir d'une idée forte : celle du mouvement. Le mouvement et son opposée : l'absence de mouvement. Le jeu doit créé une dichotomie forte entre ces deux états, favorisant l'un au profit de l'autre. Se déplacer est fortement recommandé, voire encouragé. Tandis que s'arrêter peut se révéler préjudiciable. Pour favoriser le mouvement, deux solutions ont été pensées. La première tient dans le fait qu'au repos le pitch de la musique est divisé par deux. Pour une expérience plaisante, il faut donc être en mouvement pour avoir la bonne vitesse de lecture. La seconde idée repose sur la difficulté de jeu. Le furtif au repos, la vitesse des ennemi est incroyablement rapide. Enfin, pour encore plus de juiciness et favoriser l'impression de vitesse, le player est accompagné de ghosts formant une traînée dans ces déplacements.

Le déplacement du joueur se veut réfléchi : impossible de changer de direction tant que le furtif n'a pas atteint la destination initialement choisie. Il faut donc concilier vision tactique et stratégique de l'aire de jeu en planifiant son parcours autant dans son ensemble qu'en s'adaptant aux aléas non maîtrisés rapidement. Le plaisir de jeu doit venir du skill qu'on gagne à enchaîner les mouvements d'évitement où il y a parfois pas de maîtrise mais juste de la chance !

J'aurais eu beau avoir une vision claire de ce que je souhaitais réaliser pour cette V0.1, le test utilisateur par des tiers m'a démontré qu'il y forcément des divergences d’interprétation avec mon ressenti. Les ghosts du joueur sont perçus comme un ver de terre qui rampe (le fameux snake) ! J'avoue que j'ai du mal à contredire cet argument. Enfin, difficile à doser, mais le jeu au repos mériterait d'aller vraiment beaucoup plus vite pour que l'absence de mouvement soit encore plus punitive. Oui, je suis entièrement d'accord : je veux que le joueur se voit noyé dans une jungle saturée de regards à éviter, que le moindre interstice soit une opportunité dans laquelle s'engouffrer.  

Dans les idées d'amélioration, je souhaiterai rajouter un timer. Un timer qui s'écoulerait bien sûr plus lentement quand on est en mouvement. Les speed runner seraient de nouveau favorisés. Proposer de nouveaux patterns de déplacement pour les ennemis : longitudinale, mouvements scriptés voire incluant de l'aléatoire ! Enrichir avec des idées de gameplay : des zones d'invisibilité pour les furtifs, des moyens d'influer les patterns des ennemis (rendre aveugle, les détourner), etc. Plus ambitieux, j'ai pensé faire de mes furtifs des objets multiples comme dans Loco Roco qui puissent évoluer, fusionner, voire sacrifier des parties non vitales de leur corps pour avancer.

Il faut encore l'enrobage de menu (titre, choix niveau, fin niveau, etc). Réfléchir à un éventuel scénario pour donner de la profondeur à l'univers. Et peut-être me faire violence quand à la direction artistique. Mettre le module de pubs pour faire plaisir à Raphael Grandguillot. Faire quelque chose pour les placeholders sons (musique de Super Hexagon, son d'échec et de victoire). Bref, on est encore dans le Proof Of Concept. Il ne faut pas s'emballer et garder en tête les principes de l'agilité. Même si quand on sait pas encore dans quelle direction aller, on a tendance à développer des choses qui prennent du temps mais à faible valeur ajoutée juste pour sa satisfaction personnelle. Le principe de base reste d'avoir pour chaque release une version stable, jouable, démontrant quelque chose de nouveau.
~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~

dimanche 2 avril 2017

API SensCritique, web scraping avec rvest


Contexte

Ouvert au public en 2011, SensCritique s'est vite élevé au rang de référence française du rating de contenu culturel. La pratique consiste à évaluer du contenu en partageant son avis afin de favoriser le bouche à oreille culturel. Dans le paysage du web francophone, Allociné trône en bonne place pour le 7ème art. Babelio, lui, fédère les amateurs de lecture et BetaSeries répond à la problématique majeure des binge watcher : garder le fil des séries en cours et à venir. SensCritique se veut transverse en proposant de réunir des univers représentés de manière éparse : films, séries, jeux-vidéos, livres, bds et enfin musique.

Très tôt, SensCritique a eu la volonté de proposer de nouvelles expériences utilisateurs avec dans le pipe tout un lot d'innovation. Le lab, laboratoire d'expérimentation d'applications, fut l'initiative qui affichait cette volonté d'aller plus loin avec des démonstrateurs. Sur la base des données utilisateurs et de contenu, on parlait d'outil de data visualisation (ex : afficher les corrélations entre l'âge ou le sexe du noteur et la note attribuée). Mais l'arlésienne qui nous intéresse, promise dès 2012, c'est la légendaire API publique. En 2017, la promesse tient toujours et une petite communauté, dont je fais partie, s'impatiente.

L'API publique, messie qui n'arrivera jamais

La communication de SensCritique n'est pas très claire à propos du sujet. Sur Twitter, l'espoir est régulièrement entretenu alors qu'on peut lire ailleurs que «l’équipe dit y penser depuis longtemps mais ne pas l’avoir prévu dans l’immédiat, les membres n’en étant pas demandeurs.». Cela m'a donc fait m'interroger sur les raisons de retarder sa mise à disposition. Trois axes de réflexion : les performances de l'infrastructure, l'importance de la donnée et enfin la priorité quant au business model.

Les performances

SensCritique se repose sur un très large catalogue alimenté par d'autres bases ainsi que par l'enrichissement que peut effectuer les utilisateurs eux-mêmes en mode wiki. Peut être que j'ai des problèmes de navigateur pour afficher un si riche contenu, mais de mon ressenti, le requêtage sur SensCritique est lent. Mettre à disposition une API publique implique une infrastructure solide qui puisse tenir la charge sans faire effondrer les performances globales pour l'utilisateur qui utilise le service classiquement. Cela implique aussi une politique d'utilisation du service qui permette d'éviter toutes dérives. L'API publique un sujet qui apporte plus de problématiques que de satisfaction.

La donnée

SensCritique est une mine d'or en terme de données. Mettre à disposition l'information de manière libre et gratuite, c'est perdre le monopole sur cette dernière. Pire encore, c'est donner l'opportunité à un tiers de l'exploiter à des fins commerciales (ou non) pour un service que SensCritique pourrait proposer le premier.

Le business model

SensCritique a du pour continuer à exister s'imaginer un business model. L'API publique (gratuite, j'entends) n'assurant pas de retour sur investissement, il est normal que la priorité ne se place pas en premier lieu sur ce terrain là. 

Le premier investissement a concerné la fidélisé utilisateur : enrichissement du contenu (la musique, série épisodes par épisodes, etc), ajout de nouveaux services (calendriers, posts, etc) et enfin alignement sur les nouveaux usages : appli mobile Andoid (ios toujours en cours de développement). Pour la petite histoire, la problématique de la mobilité avait été mal adressée au départ avec une copie du site version mobile.

Le second investissement (à l'instar de Facebook pour les professionnels) consiste à exploiter les données des utilisateurs pour proposer à ces derniers et aux professionnels (annonceurs) un service adapté. On notera que l'intégration de la publicité ciblée dans le site est plutôt bien pensé. La première, invasive, se traduit par un bandeau qui permet la diffusion de vidéos qui invite l'utilisateur à rajouter le contenu dans sa liste d'envie. Enfin, la seconde se traduit par un système de notifications et de participation à des concours. La publicité est ludique, au service de l'internaute.


Back-up de mes données

Mon besoin n'est pas directement lié avec la mise à disposition d'une API. Cependant une API favoriserait beaucoup sa résolution. En effet, en tant qu'utilisateur de SensCritique, je confie au service de la donnée en attribuant des notes aux œuvres référencées. Mon souhait est de pouvoir externaliser cette donnée (dans excel) pour la savoir en sécurité chez moi puis à terme la manipuler. Ce que le site ne propose pas actuellement. Pour répondre à mon besoin, j'ai choisi d'utiliser la librairie rvest (à prononcer harvest, la moisson en français) pour mettre en pratique mes connaissances acquises sur R (dans le cadre du machine learning) et pour la simplicité (et rapidité) d'adressage qu'elle m'apporte du fait qu'il suffit juste d'un client R sur mon poste.

Un bref état de l'art permet d'observer que la communauté de membres (NitriKx/senscritique-api, disceney/SensCritiqueAPI, thcolin/senscritique-api) ne reste pas inactive. L'analyse du code montre néanmoins que les solutions sont soient partielles soient obsolètes (se reposent sur une structuration html que le site a fait évoluer depuis). De plus, monter une solution autour de wamp pour faire de l'extraction de données one-shot me paraît être une perte d'énergie. Des services en ligne existent comme dexi.io offrant la possibilité de mettre en place un pipeline d'extraction s'achevant sur de l'écriture dans une spreadsheet google. Bonne idée pour une solution qui se veut industrialisée. Enfin, pour un usage court-termiste, Chrome propose aussi son plugin Web Scraper. 

Web scraping de SensCritique avec rvest, ses limites

Principe
La fonction principale (de rvest) sur lequel se repose mon script est html_nodes. A partir d'un sélecteur xpath, on extrait le noeud (titre, date, type, artiste, note, infos complémentaires) qui nous intéresse pour l'ensemble des items (œuvres listées dans ma bibliothèque) affichés sur les pages de ma collection d’œuvres.

Limite et contournements
SensCritique étant une base de données en perpétuelle complétion, certains items peuvent être partiellement complétés : une date peut venir à manquer. Points noirs discutés sur les forums, html_nodes n'est pas en mesure de détecter un noeud manquant : il va simplement lister un à un les nœuds qu'il rencontre. La première alternative, moyennement performante, consiste à extraire à la main (sans rvest) les items pour les parser chacun à leur tour. La seconde, que j'ai employé, tire profit du mode wiki de SensCritique (sauf pour la musique) : c'est win-win, je complète ce qui manque dans la base du site en assurant d'une meilleure qualité de ce que j'extraie.

Ethique
Dernier point de vigilance sur la moralité de l'entreprise. Avec un rapide coup d’œil dans les CGU, SensCritique n'évoque rien à propos de la consommation de ses données et l'éventuelle sollicitation de ses serveurs par un robot. Dans le doute, pour ne pas faire apparaître la démarche comme du hacking, on sollicite les serveurs avec un délai raisonnable pour éviter un éventuel blocage ip. Enfin, on utile l'outil pour son usage strictement personnel. Voila, bon scraping !

Afficher / Masquer
library(rvest) library(stringr) # Paramétrage utilisateur <- "Lukeskyforges" page_init <- 1 page_fin <- 2 # Préparation URL de scrapping url_sens <- "https://www.senscritique.com/" url_end <- "/collection/all/all/all/all/all/all/all/all/list/page-" url_pre <- paste(url_sens, utilisateur, url_end,sep="") for (i in page_init:page_fin) { # Log start cat("Page ", i," en cours de traitement\n") # Wait wait <- sample(3:10, 1) Sys.sleep(wait) # Téléchargement de la page i url <- paste(url_pre,i,sep="") fichier <- paste("scrapedpage-",i,".html",sep="") download.file(url, destfile = fichier, quiet=TRUE) content <- read_html(fichier) # Extraction titre title <- html_nodes(content, xpath="//h2[contains(@class, 'elco-title')]/a/text()") %>% html_text(title) # Extraction date date <- html_nodes(content, xpath="//span[contains(@class, 'elco-date')]/text()") %>% html_text(date) %>% str_replace_all("\\(" , "") %>% str_replace_all("\\)" , "") # Extraction type pattern="/(.*?)/" type <- html_nodes(content, xpath="//h2[contains(@class, 'elco-title')]/a/@href") %>% html_text(type) type <- data.frame(matrix(unlist(regmatches(type, regexec(pattern,type))), nrow=2))[2,] type <- unname(unlist(type)) # Extraction artiste artiste <- html_nodes(content, xpath="//a[contains(@class, 'elco-baseline-a')][1]/text()") %>% html_text(artiste) # Extraction note note <- html_nodes(content, xpath="//span[contains(@class, 'elrua-useraction-inner only-child')]/text()") %>% html_text(note) note <- note[note != "\t\t\t\t"] note <- str_replace_all(note, "[\r\n\t]" , "") note <- replace(note, note=="", "Envie") note <- matrix(unlist(note), ncol=3, byrow= TRUE) note <- note[,-c(1,2)] note # Extraction commentaire commentaire <- html_nodes(content, xpath="//p[contains(@class, 'elco-baseline elco-options')]") %>% html_text(commentaire) %>% str_replace_all("[\r\n\t]" , "") # Consolidation du tableau df_page <- data.frame(title, note, date, artiste, type, commentaire, i) colnames(df_page) <- c("Titre","Note", "Date de sortie", "Auteur/Artiste/Réalisateur", "Categorie", "Détails", "Page") # Export dans excel (ajout entête pour première page) if (i == 1) { write.table(df_page, file = "bib.csv", sep = ";", row.names = F, qmethod = "double") } else { write.table(df_page, file = "bib.csv", append = TRUE, sep = ";", row.names = F, col.names = F, qmethod = "double") } # Log end cat("Page ", i," extraite\n") }

mercredi 6 mai 2015

Arduino, réaliser un objet connecté sans Shield


Objectif :

Après une rapide prise en main de l'Arduino à travers les exemples présents dans le Arduino Starter Kit, il m'est venu la nécessité d'expérimenter quelque chose de plus personnel. Je me suis donc décidé à réaliser un objet connecté. Le but est de transmettre de l'information propre à la carte jusqu'à son back-end et inversement permettre à un utilisateur connecté d’interagir avec la carte depuis une interface distante dédiée. Même que l'objet connecté devra s'interface à travers une API pour communiquer avec le back-end.

Pour mettre en place la connectivité entre l'Arduino et le web, il y a des shields qui facilitent grandement le travail (Ethernet, Wifi voire même Akeru pour interagir avec le réseau Sigfox même si je ne suis pas certain que ça gère une communication descendante vers leur modem...). Mais voila, j'ai pas tout ça. Comme rien est impossible, l'outil de développement Processing s'est révélé idéal pour la tâche à accomplir (IDE similaire à celle de l'Arduino, on ne sent pas dépaysé) malgré la perte d'autonomie de mon objet connecté (échange via USB entre la carte et l'appli Processing qui tourne en tâche de fond).

Aucune idée intelligente arrêtée pour un quelconque use case, je me contente d'une démonstration technique pour voir que ça marche. L'objet sera donc constitué de trois leds (rouge, bleue et verte) et un bouton poussoir. L'utilisateur connecté sera en mesure de choisir une couleur sur une interface distante, la led associée sur la carte s'allumera. J'ai fait le choix que mon objet connecté (via Processing) soit uniquement un client web. Le client sollicitera l'API du back-end de façon régulière (toutes les minutes) ou de manière forcée par l'appui du bouton situé sur la carte.

Architecture :


Ca me parait clair, non? On notera quand même la présence d'un fichier de stockage qui fera office d'EEPROM car la carte Arduino seule ne peut pas stocker de données non volatiles (autre que le code qu'on flash). Ce fichier contiendra un numéro de transaction qui sera transmis à l'API.

Implémentation :

Arduino : On ne va pas s'attarder sur le montage physique. Trois leds avec des résistances de 220, le bouton poussoir avec celle de 10k, chaque composant relié à une entrée/sortie digitale du micro-contrôleur. Concernant sa programmation, pas bien compliqué non plus, on initie une transmission série. On reste à l'écoute d'un ordre d'allumer une led et on envoie un signal quand on détecte l'appui sur le bouton poussoir.

Afficher / Masquer
const int buttonPin = 2; const int ledPin = 13; const int greenLed = 3; const int blueLed = 4; const int redLed = 5; int buttonState = 0 ; int ordre = 0; void setup() { pinMode(buttonPin, INPUT); pinMode(ledPin, OUTPUT); pinMode(greenLed, OUTPUT); pinMode(blueLed, OUTPUT); pinMode(redLed, OUTPUT); Serial.begin(9600); } void loop() { // La gestion des leds if (Serial.available()>0){ ordre = Serial.read(); if (ordre == 2){ digitalWrite(greenLed,LOW); digitalWrite(blueLed,LOW); digitalWrite(redLed,HIGH); } else if (ordre == 3){ digitalWrite(blueLed,HIGH); digitalWrite(redLed,LOW); digitalWrite(greenLed,LOW); } else if (ordre == 4){ digitalWrite(redLed,LOW); digitalWrite(blueLed,LOW); digitalWrite(greenLed,HIGH); } } // La gestion du bouton buttonState = digitalRead(buttonPin); if (buttonState == 1){ digitalWrite(ledPin,HIGH); Serial.println("1"); delay(1000); } else{ digitalWrite(ledPin,LOW); } }
Processing 2.2.1 : A l'exact opposé du code pour l'Arduino, on écoute l'appui sur le bouton et on transmet les ordres d'allumer une led. L'obtention de numéro courant de transaction se fera grâce à la fonction loadStrings tout comme sa mise à jour avec saveStrings. Le plus intéressant reste la mise en place du client web (que j'ouvre pour chaque transaction du fait qu'il se déconnecte très vite). Le format de la requête étant très précis, il faut penser à calculer la taille du body (en l’occurrence ici le nom de l'opération appelée, le numéro de transaction et le type d'appel (bouton, schédulé) transmis à l'API).

Afficher / Masquer
import processing.net.*; import processing.serial.*; // Objet de connexion Client clientWeb; Serial portSerial; // Variables de travail String dataClientWeb; String dataSerial; int ordre; int numeroTransaction = 0; String linesEeprom[] ; char couleur ; int savedTime; int totalTime = 60000; // Constantes final int ORDRE_BOUTTON = 1; final int ORDRE_LED_ROUGE = 2; final int ORDRE_LED_BLEU = 3; final int ORDRE_LED_VERT = 4; final char TYPE_ACTION_BOUTON = 'B'; final char TYPE_ACTION_SCHEDULEE = 'S'; void setup() { // On récupère le numéro de la transaction en cours depuis l'EEPROM linesEeprom = loadStrings("C:\\Users\\MonUser\\Documents\\Arduino\\Projets\\projetPerso_AppelAPI\\eeprom.txt"); numeroTransaction = Integer.parseInt(linesEeprom[0]); // Définition port Série String portName = Serial.list()[0]; portSerial = new Serial(this, portName, 9600); } void draw() { // Gestion au timer int passedTime = millis() - savedTime; if (passedTime > totalTime) { savedTime = millis(); gestion_numero_transaction(); // Ouverture client HTTP clientWeb = new Client(this, "monserveur_distant.net", 80); couleur = call_get_color(TYPE_ACTION_SCHEDULEE); allumer_led(couleur); clientWeb.stop(); } // Gestion appui du bouton if (portSerial.available() > 0){ dataSerial = portSerial.readStringUntil('\n'); // On récupère les infos de la carte if (dataSerial!=null) { // On récupère l'ordre envoyé par l'Arduino ordre = Integer.parseInt(dataSerial.substring(0, dataSerial.length()-2)); print("Ordre : "); println(ordre); gestion_numero_transaction(); // Si on détecte un appui de bouton alors on envoi la requête POST à l'API if (ordre == ORDRE_BOUTTON){ // Ouverture client HTTP clientWeb = new Client(this, "monserveur_distant.net", 80); couleur = call_get_color(TYPE_ACTION_BOUTON); allumer_led(couleur); clientWeb.stop(); } } } } char call_get_color(char type_action) { // On créé le body String body = "act=get_color&num="+str(numeroTransaction)+"&type="+str(type_action); println("On appelle le get_color !"); clientWeb.write("POST http://monserveur_distant.net/arduino/arduino_api.php HTTP/1.1\r\n"); clientWeb.write("Host: monserveur_distant.net\r\n"); clientWeb.write("Content-Type: application/x-www-form-urlencoded\r\n"); clientWeb.write("Content-Length: "+body.length()+"\r\n"); clientWeb.write("\r\n"); clientWeb.write(body); clientWeb.write("\r\n"); // Attente d'une seconde delay(1000); dataClientWeb = clientWeb.readString(); char c = dataClientWeb.substring(dataClientWeb.indexOf("color:")+6,dataClientWeb.indexOf("color:")+7).charAt(0); print("Couleur : "); println(c); return c; } void allumer_led(char c){ if (c == 'r') { // Allumer led rouge portSerial.write(ORDRE_LED_ROUGE); } else if (c == 'b') { // Allumer led bleu portSerial.write(ORDRE_LED_BLEU); } else if (c == 'v') { // Allumer led verte portSerial.write(ORDRE_LED_VERT); } } void gestion_numero_transaction(){ // On incrémente le numéro de la transaction numeroTransaction = numeroTransaction + 1; print("Numero transaction : "); println(numeroTransaction); // On enregistre le numéro sur l'eeprom String listNumero[] = {str(numeroTransaction)}; saveStrings("C:\\Users\\MonUser\\Documents\\Arduino\\Projets\\projetPerso_AppelAPI\\eeprom.txt", listNumero); }
API : C'est le gros point faible de mon système. Pas d'authentification/identification, ni d'échange sécurisé. Ca se veut API REST, on y accède par POST HTTP. J'ai même pas eu le courage d'encoder la réponse en JSON pour ne pas avoir à me pencher sur son décodage côté Processing.

Afficher / Masquer
<?php // Encodage en UTF-8 header('Content-Type: text/html; charset=utf-8'); $act = $_POST['act']; $numero = $_POST['num']; $type_action = $_POST['type']; include 'utils/is_dev.php'; include 'utils/fonctions_db.php'; include 'utils/fonctions_arduino.php'; if ($act == "get_color") { // On renvoie la valeur $db = db_connect(); $couleur = get_couleur($db); set_trace($numero, $couleur, $type_action, $db); echo "color:".$couleur; } ?>
Interface : J'ai essayé de réaliser quelque chose de sexy et minimal. Vu que le type color de html5 ne permet pas de customisage, j'ai utilisé le module customColorPicker pour permettre la sélection des trois couleurs proposées à l'utilisateur. Simple à prendre en main, je l'ai choisi avant tout pour son design sobre. Je me suis de plus payé le luxe de rajouter de l'AJAX quant à l'affichage de l'historique pour qu'il apparaisse sans refresh de la page. Ca a été particulièrement douloureux, jQuery pouvant être parfois très mystérieux. J'ai penché pour le module utf8.js-master pour gérer l'affichage des caractères spéciaux depuis Javascript.


Le php :

Afficher / Masquer
<?php // Encodage en UTF-8 header('Content-Type: text/html; charset=utf-8'); // On regarde si on est en mode développement include 'utils/is_dev.php'; include 'utils/fonctions_db.php'; include 'utils/fonctions_arduino.php'; // On lance l'échanges avec la bdd $db = db_connect(); $trace = get_trace($db); $nb_trace = count($trace)-1; $couleur = get_couleur($db); $carre_couleur['r'] = "<span class=\"colorbox\"><b style=\"background:#D42436\"></b></span>"; $carre_couleur['b'] = "<span class=\"colorbox\"><b style=\"background:#00CED1\"></b></span>"; $carre_couleur['v'] = "<span class=\"colorbox\"><b style=\"background:#74d600\"></b></span>"; $type['S'] = "Mise à jour schédulée"; $type['B'] = "Mise à jour au bouton"; ?> <html> <head> <link rel="stylesheet" media="screen" href="css/arduino.css"> <script type="text/javascript" src="script/jquery-1.9.0.min.js"></script> </head> <body id="css-zen-garden"> <div class="page-wrap"> <section class="intro" id="zen-intro"> <table id="tab"> <tr><td COLSPAN=2><h3>Aide <br/></td><tr> <tr><td COLSPAN=2> ><?php echo utf8_encode("♣ Interface connectée à une carte Arduino dôtée de trois leds (rouge, bleu, vert).<br/> ♣ Clickez sur la couleur de votre choix dans «Selection» pour changer la couleur de la led de l'Arduino.<br/> ♣ «Historique» trace les mises à jour des leds sur la carte Arduino. Elles peuvent êtres schédulées (toutes les minutes) ou déclenchées par l'appui du bouton situé sur la carte.<br/><br/><br/>"); ?> </td><tr> <tr><td><h3>Selection <br/></h3></td> <td><h3>Historique <br/></h3></td></tr> <tr> <td> <span class="colorpicker"> <span class="bgbox"></span> <span class="hexbox"></span> <span class="clear"></span> <span class="colorbox"> <b name="couleur" id-valeur="r" <?php if ($couleur == "r") { echo "class=\"selected\""; } ?> style="background:#D42436" title="Rouge"></b> <b name="couleur" id-valeur="b" <?php if ($couleur == "b") { echo "class=\"selected\""; } ?> style="background:#00CED1" title="Bleu"></b> <b name="couleur" id-valeur="v" <?php if ($couleur == "v") { echo "class=\"selected\""; } ?> style="background:#74d600" title="Vert"></b> </span> </span> </td> <td> <?php for ($i=1; $i<=$nb_trace; $i++) { echo utf8_encode("<p>".$trace[$i]['numero'].' '.$carre_couleur[$trace[$i]['couleur']].' - '.$type[$trace[$i]['type']].' à '.$trace[$i]['date'].'<br/></p>') ; } ?> </td> </tr> </table> </section> </div> <link href="colorpicker/customColorPicker.css" rel="stylesheet" type="text/css" /> <script src="colorpicker/customColorPicker.js" type="text/javascript"></script> <script src="script/utf8.js-master/utf8.js" type="text/javascript"></script> <script src="script/arduino.js" type="text/javascript"></script> </body> </html>
Le Javascript :

Afficher / Masquer
$(document).ready( function() { // Ajoute une nouvelle ligne à l'historique (et supprime la dernière) toutes les 10s si nécessaire setInterval(function () { $.ajax({ type: "POST", url: "act_arduino.php", data: "act=get_trace", success: function(msg){ var o = JSON.parse(msg); var msg_parsed = Object.keys(o).map(function(k) { return o[k] }); var screen = $('p:first-child').text(); var num_screen = screen.substring(0,screen.indexOf("-")-2); var num_base = msg_parsed[0].numero; if (num_screen != num_base) { if (msg_parsed[0].type == "S"){ var type = "Mise \xC3\xA0 jour sch\xC3\xA9dul\xC3\xA9e"; } else if (msg_parsed[0].type == "B"){ var type = "Mise \xC3\xA0 jour au bouton"; } if (msg_parsed[0].couleur == "r"){ var couleur = "<span class=\"colorbox\"><b style=\"background:#D42436\"></b></span>"; } else if (msg_parsed[0].couleur == "b"){ var couleur = "<span class=\"colorbox\"><b style=\"background:#00CED1\"></b></span>"; } else if (msg_parsed[0].couleur == "v"){ var couleur = "<span class=\"colorbox\"><b style=\"background:#74d600\"></b></span>"; } var child = '<p>'+couleur+msg_parsed[0].numero+' - '+type+' \xC3\xA0 '+msg_parsed[0].date+' <strong>NEW!</strong></p>'; $('p:first-child').before(utf8.decode(child)); $('p:last-child').remove(); } }, error: function(msg){ } }); }, 10000); // Post synchrone de la mise à jour de la couleur $("b[name = 'couleur']").click(function(){ post_couleur = $(this).attr('id-valeur'); postData('act_arduino.php',{couleur:post_couleur,act:"maj_couleur"}); }); // Réalise un post synchrone function postData(page,data) { var form = document.createElement('form'); form.setAttribute('action', page); form.setAttribute('method', 'post'); for (var n in data) { var inputvar = document.createElement('input'); inputvar.setAttribute('type', 'hidden'); inputvar.setAttribute('name', n); inputvar.setAttribute('value', data[n]); form.appendChild(inputvar); } document.body.appendChild(form); form.submit(); } } );
Back-end : Une table pour historiser les appels à l'API. Une autre pour stocker la couleur choisie par l'utilisateur. Des fonctions php pour requêter tout ça.

Demo :


Use case 1 :
- Sélectionner une couleur de led dans l'interface
- Presser le bouton poussoir et observer la led de la couleur choisie s'allumer
- Observer dans l'interface la trace de la mise à jour

Use case 2 :
(- Sélectionner une couleur de led dans l'interface)
- Attendre 1min
(- Observer la led de la couleur choisie s'allumer)
- Observer dans l'interface la trace de la mise à jour

Axe d'amélioration :

- Faire de l'API le point central de l’interaction avec le back-end. Dans mon cas, les fonctionnalités de mise à jour de la couleur et de l'obtention de l'historique présentes dans l'interface sont effectués directement avec le back-end. L'API gagnerait à être enrichie pour servir de base à la création d'une nouvelle interface par exemple.

- Monter une API sécurisée. Proposer une authentification (Basic, Digest ou OAuth) et envisager la sécurisation de l'échange avec SSL. J'ai vu que c'était impossible de mettre en place l'implémentation associée directement sur l'Arduino (avec les shield wifi et ethernet) du fait de la trop faible mémoire (SRAM) du micro-contrôleur. Peut-être est-ce possible via Processing ? Il n'empêche qu'il y a un trou dans la raquette en terme de sécurisation si on se projette avec l'Arduino pour réaliser un objet connecté.

- Réfléchir à l'alternative de transformer Processing en server web pour permettre à l'utilisateur de le solliciter quand il le souhaite (et non de façon récurrente comme effectuée en tant que client). Il est possible d'y héberger une interface minimale mais le travail pour la rendre dynamique semble une perte de temps. A minima, on peut envisager d'y héberger un point de connexion (API) mais ce dernier resterait figé.

- Mettre en place une meilleure gestion de l'appel à l'API. En l'occurrence, actuellement on ne gère aucun des codes retour HTTP. De plus, le delay d'1s après l'envoi de la requête en espérant recevoir la réponse est un peu limite.

- Réfléchir à un use case intelligent. Allumer des leds à distance, c'est bien, mais ça ne sert pas à grand chose.