09/06/2018

Quand Spoggy s'attaque à Persée, Retour après la bataille

Bon dans le dernier article ( Quand Spoggy utilise Polymer pour s'attaquer à Persée... ), nous vous avions fait part de la stratégie que Spoggy comptait adopter pour s'attaquer à Persée...

Pour tout vous dire, la vraie attaque a eu lieu le weekend dernier, à la Cuisine du Web  et était organisée par l'équipe de Persée, lors de son premier hackathon : PerséeUP ... A noter également la complicité de l'équipe MuseomixAura et de quelques autres intervenants...

Super accueil, super conditions, super tout tout tout... Un hackathon tellement sympa qu'on aurait presque pu le faire en famille, m'enfin, je me comprend 😜...

Pour ceux qui débarque après la bataille, (et oui, il y en aura toujours, et c'est certainement mieux ainsi), il convient de rappeler ce qu'est Spoggy et en quoi l'assaut de Persée était une phase importante de son développement...

D'abord, Spoggy s'inscrit dans un projet plus vaste et a pour objectif de faciliter l'utilisation des données au format RDF pour tout un chacun, pour mon fils de 7 ans ou ma mère de .... non, restons corrects...

Pour le projet Smag0, il nous est en effet apparu plus judicieux de partir sur le format de données RDF qui permet l'interconnexion de bases de connaissances...



Chaque objet connecté, chaque robot possède une petite base de connaissances au format rdf ou un endpoint sparql...
(Fuseki est un simple fichier Zip à décompresser & ne nécessite que java8).

Spoggy doit être capable de se connecter à plusieurs triplestores ou endpoints, tels qu'un serveur Fuseki personnel, DBPedia, ou d'autres format comme celui par exemple de Semantic Forms

Spoggy correspond à l'interface utilisateur (en vert) vers les triplestore, et le triplestore Persée correspond à ce format, il est l'un des "Autres serveurs et sourtces RDF" (cadre rouge sur le schéma). 
Spoggy permet d'une part de créer des graphes RDF , mais la partie qui nous intéresse ici, c'est la restitution des informations sous forme visuelle... Et l'adaptation à Persée était un peu l'épreuve du feu pour Spoggy...
Lors du hackathon, plusieurs projets ont émergé dont un qui avait pour objectif de faciliter l'exploration des données Persée, par la navigation sous forme de graphe, en représentant les noeuds et les liens qui les unissent... C'était la brèche, je me suis engouffré avec mon Spoggy...

Une équipe au top, avec une Françoise passionnée de RDF, un Max chercheur-utilisateur, une Cécile qui avait déjà réfléchi a la visualisation/ navigation, un Karl et Baptiste développeurs, ça c'est pour les compétences générales, mais bien d'autres cachées... Pour l'occasion, j'avais même invité notre Jean-Marc, garant national de la régularité du RDF, qui ne s'est pas géné pour bousculer un peu l'équipe de Persée... 😏

Avec tout ça, les idées ont fusé, les post-it collés, on décollé, les marqueurs ont chauffé sur les dashboards... Une cohésion s'est installée rapidement entre des personnes qui ne s'étaient jamais rencontrées avant... C'est ça la force des hackathons...


Bon et concrètement qu'est ce que ça donne ?

Grâce aux requêtes de Karl et Cécile, Spoggy permet maintenant de naviguer de trois façons différentes dans les données de Persée : 
(a noter une fonctionnalité développée lors du hackathon n'a pas encore été intégrée dans les vidéos qui suivent, c'est l'affichage de la page correspondante de Persée, Persée semble bloquer l'inclusion de ses pages dans une iframe ? mais l'intégration d'un plugin Spoggy dans les pages Persée pourrait résoudre cet ecueil)

1 En utilisant une url paramétrée pour Spoggy :  


La première idée était d'inclure "un graphe de navigation" dans les pages de Persée, mais ne pouvant évidemment pas intervenir sur ces pages (à voir si un jour on pourra), nous avons fait l'inverse : inclure les données de Persée dans Spoggy. La transition peut être effectuée au moyen d'un bouton dans la page de Persée qui renverrait vers Spoggy avec comme paramètres les informations que l'on veut afficher : 

Attention, pour se brancher sur Persee, vous devez consulter Spoggy en http et non https !

par exemple la page Persée sur Pierre Bourdieu  (http://www.persee.fr/authority/30551 ) aurait un bouton qui renverrait vers Spoggy, ou afficherait le graphe via cette adresse (remplacer 'www' par 'data et 'authority' par 'person' : 


Pour un document tel que http://www.persee.fr/doc/ahess_0395-2649_1977_num_32_3_293828, remplacez simplement 'www' par 'data' : 





2 : Mode Exploration de Spoggy appliqué à Persée : 

En utilisant les "Requêtes prédéfinies de Karl & Cécile "





3: Spoggy en mode recherche textuelle sur les données de Persée : 






Et Maintenant ???

Il y a encore quelques requêtes à revoir, des filtres, et des aménagements...
petit rappel : 
- pour participer au développement de Spoggy, c'est sur le github heroku-spoggy
- pour apporter de nouvelles idées : utilisez le kanban de heroku-spoggy

A l'issue de tout ce travail, il semble important que les fournisseur de données via des triplestore, fournissent à leur utilisateurs, des points d'entrée, des requêtes génériques de : 
- exploration & de recherche ( en jaune dans le code ci-dessous),
- une fonction pour parser les résultats associée aux requêtes précédentes, pour transformer ces résultats en noeuds/liens utilisables par un outil de visualisation. (en vert dans le code ci-dessous),
- et des requêtes complémentaires en fonction du type de noeud sur lesquels on clic (en bleu dans le code ci-dessous)

Ceci devrait être faisable si les fournisseurs de données via triplestore fournissaient également un fichier json de présentation, de requêtes, de fonctions de parsing et de tout outil qui pourrait aider à explorer leur triplestore... 
Un exemple de ce à quoi cela pourrait ressembler est (a l'heure actuelle dans le fichier spoggy-endpoint, mais c'est certainement amené à changer pour généraliser la proicédure ;-) : 

{
name:'Persee',
description: "Le portail Persée\n \
Persée est un programme national de numérisation et de diffusion de collections de documents scientifiques.\
Sur son portail (www.persee.fr) plus de 200 collections de revues publiées de 1820 à nos jours sont disponibles.\
Le portail permet une recherche dans certaines métadonnées et le texte de plus de 700 000 documents et 500 000 illustrations. \
Les interfaces de recherche actuelles permettent d’exploiter trois index (documents, illustrations et auteurs) \
et elles sont dotées de fonctionnalités de tri, de facettes, etc.",
plus: 'http://data.persee.fr/ressources/le-triplestore-de-persee/',
url: 'http://data.persee.fr/sparql', // url: 'http://data.persee.fr',
image: 'https://www.persee.fr/static/persee.png',
actif: true,
selectionnable: true,
type: 'virtuoso',
read:true, write:false,
exploreQuery: {
query: 'SELECT DISTINCT ?s ?t ?sub \
WHERE {\
?s ?p bibo:Document.\
?s dcterms:title ?t.\
?s dcterms:subject ?sub.\
filter (lang(?sub) = "" || langMatches(lang(?sub), "fr"))\
} LIMIT 100' ,
typeResources:{
s: 'bibo:Document',
sub: 'Subjects'
},
parseToVis : function(app, results, endpointUrl,typeResources, group){
// var app = this;
var visRes = {nodes:[], edges:[]};
results.forEach(function(r){
console.log(r);
let node = {
id: app.visId(r.s.value),
type: null,
resourceType: typeResources.s,
uri: r.s.value,
label: app.visLabelSplit(app.visLabel(r.t.value)),
endpointUrl: endpointUrl,
color: "rgb(76,0,74)",
font: {
color: 'rgb(255,255,255)'
},
cid: group,
y: 2*Math.random()
}
var trouveN = visRes.nodes.find(function(element){
return node.id == element.id
});
if (trouveN) {
// console.log("TROUVE ")
// console.log(trouveN)
node = trouveN;
}else{
visRes.nodes.push(node);
}
let nodeSub = {
id: r.sub.value,
type: r.sub.type,
resourceType: typeResources.sub,
label: app.visLabelSplit(r.sub.value),
endpointUrl: endpointUrl,
color: "rgb(107,142,35)",
shape: "box",
cid: group,
y: 2*Math.random()
}
var trouveS = visRes.nodes.find(function(element){
return nodeSub.id == element.id
});
if (trouveS) {
// console.log("TROUVE ")
// console.log(trouveS)
nodeSub = trouveS;
}else{
visRes.nodes.push(nodeSub);
}
let eType = {from : node.id, to: nodeSub.id, label: "subject"}
if (!visRes.edges.includes(eType)){
visRes.edges.push(eType)
}
});
return visRes;
}
},
searchQuery : {
query: ' SELECT distinct * WHERE { ?s rdfs:label ?label . \
?s rdf:type ?type . \
OPTIONAL { ?s dcterms:title ?title .} \
FILTER(bif:contains(?label, "RECHERCHE")) . \
} LIMIT 100',
typeResource:'bibo:Document'
},
clickonNode: [
{type : "Personne",
detecteur: "http://xmlns.com/foaf/0.1/Person",
requete : 'PREFIX dcterms: <http://purl.org/dc/terms/> \
PREFIX foaf: <http://xmlns.com/foaf/0.1/> \
SELECT DISTINCT ?person ?name \
WHERE { \
?doc dcterms:creator <NODEID>. \
?doc dcterms:creator ?person. \
?person foaf:name ?name. \
}',
lienCree : "co-auteur"
},
{type : "Document",
detecteur: " a revoir",
requete : 'a revoir',
lienCree : "a revoir"
}
]
/* exploreQuery: 'select distinct ?Concept where {[] a ?Concept} LIMIT 100'*/
},
  




Vous retrouverez les développements complémentaires effectués lors du hackathon dans la branche Persée du repo github de Spoggy










27/05/2018

Quand Spoggy utilise Polymer pour s'attaquer à Persée...

Difficulté du tuto ? ça devrait être accessible à toute personne qui a un peu approché le javascript...



Dans ce petit tutoriel, on va voir comment utiliser Polymer ( plus d'infos ) pour effectuer des requêtes vers un endpoint sparql et afficher les résultats, on va ici utiliser :


mais le principe est le même avec d'autres endpoints tels que :


... et bien d'autres  ou encore
C'est quoi un endpoint Sparql ?
et un triplestore ? un triplet ? un  prédicat ? un vocabulaire ?


ETAPE 1 : INSTALLER LA STRUCTURE D'UNE APPLI POLYMER

ETAPE 2 : CREATION DE L'ELEMENT SPOGGY-SPARQL QUI VA EFFECTUER LES REQUETES VERS LE ENDPOINT

ETAPE 3 : LA REQUETE VERS L'ENDPOINT SPARQL.

ETAPE 4 : LANCER LA REQUETE ET RECUPERER LES RESULTATS.

ETAPE 5 : AFFICHAGE DES RESULTATS SOUS FORME DE LISTE.


ETAPE 1 : INSTALLER LA STRUCTUTRE D'UNE APPLI POLYMER



Prérequis :
Ouvrez un terminal / invite de commande et installez en global (-g) Polymer  : 
  • npm install -g polymer-cli

Nous utilisons ici Polymer 2 car à l'heure où l'on écrit ces lignes, Polymer 3 n'est pas encore stabilisé.


Créez un dossier pour votre appli  et un dossier 'public' à l'intérieur de celui-ci et installez l'appli exemple de Polymer: 
  • mkdir tutosparql && cd tutosparql
  • mkdir public && cd public
  • polymer init
  • choisissez '6) polymer-2-starter-kit'


lancez polymer serve



Votre appli 'starter-kit' devrait normalement être accessible à l'adresse http://127.0.0.1:8081 



Utilisez maintenant votre éditeur préféré pour changer le code de cette appli

Le fichier principal de notre application se trouve dans 'public/src' et se nomme 'my-app.html'




Dans le code du fichier 'my-app.html', on constate l'import des modules 'my-view1', 'my-view2' et 'my-view3', on va utiliser et modifier 'my-view1' qui se trouve être la page d'accueil de notre appli.

Commençons par créer un nouvel élément que nous allons ensuite inclure dans la page 'my-view1'.
Arrétez le server (Ctrl+C) ou ouvrez un nouvel invite de commande/ terminal, placez vous dans le dossier 'public/src', créez un dossier pour votre nouvel élément, lancez à nouveau 'polymer init' et choisissez 'polymer-2-element'




Ceci vous créé un élément nommé 'spoggy-sparql' dans 'public/src'. 
Commencez par modifier la première ligne du fichier 'public/src/spoggy-sparql/spoggy-sparql.html' (on utilise un dossier public, qui n'est pas réellement la norme, donc il faut ajouter '../' pour remonter d'un niveau de plus et aller chercher 'polymer' dans le dossier 'bower_components'  ;-) ) : 

<link rel="import" href="../polymer/polymer-element.html">
modifié en :
<link rel="import" href="../../bower_components/polymer/polymer-element.html">



Maintenant que notre élément est créé, on va l'insérer dans la page 'my-view1.html'
Pour importer notre element spoggy-sparql, il faut l'importer en incluant la ligne : 

<link rel="import" href="./spoggy-sparql/spoggy-sparql.html">

et ensuite le positionner entre les balises <template> .... </template>




Ce qui nous donne en consultant la page http://127.0.0.1:8081 (si vous avez arrêté le serveur avec Ctrl+C, relancez-le avec 'polymer serve'




Jusque là, tout va bien, attention, ça se corse...


ETAPE 2 : CREATION DE L'ELEMENT SPOGGY-SPARQL QUI VA EFFECTUER LES REQUETES VERS LE ENDPOINT


Bon passons aux choses sérieuses. Maintenant que notre structure d'appli est prête, on va pouvoir utiliser tout le potentiel de Polymer, et notamment la réutilisation des web-components ou elements...

On va par exemple avoir besoin du composant <iron-ajax> qui nous permettra d'effectuer les requêtes ajax asynchrones...

Sans plus tarder, entrons dans le vif du sujet : 
Placez-vous dans le dossier 'public' et installez le composant 'iron-ajax' : ( si vous n'avez pas encore installé bower, installez-le maintenant en global avec la commande 'npm install -g bower' ) 


 Vous pouvez retrouver les composants installés dans le dossier 'public/bower_components'
Maintenant que le composant iron-ajax est installé, on va pouvoir l'inclure dans notre composant 'spoggy-sparql'... C'est bon ? vous suivez toujours ???

De la même façon que tout à l'heure, il faut importer le composant avec la ligne : 

<link rel="import" href="../../bower_components/iron-ajax/iron-ajax.html">

et le placer entre les balises <templates></templates>

Ici, rien n’apparaît car le composant <iron-ajax> ne sert qu'à faire des requêtes, il n'a pas de 'visuel', mais on s'occupera de ça plus tard.



Tout va bien ? 
On attaque maintenant la requête

ETAPE 3 : LA REQUETE VERS L'ENDPOINT SPARQL.

Donc, ce qui nous intéresse, Thérèse, c'est d'aller chercher des data à l'adresse http://data.persee.fr/sparql . Ne soyons pas plus royalistes que ceux qui le sont déjà, sur cette page, il y a déjà un exemple de reqûete que voici : 

select distinct ?p where {?p a foaf:Person} LIMIT 100

On va pas aller chercher plus loin pour l'instant...
Mais il va falloir paramétrer notre élement <iron-ajax>.
le composant iron-ajax a en effet besoin de quelques paramètres : 
  • id : un identifiant, ça ne fait pas de mal, et on en aura besoin plus tard pour lancer la requête
  • url : une url, pour savoir à quelle adresse effectuer la requête
  • param : des options qui vont contenir le corps de la requête
  • handle-as : on défini le format dans lequel on souhaite recevoir le résultat de la requête
  • on-response : la fonction à exécuter lorsque un résultat est disponible (Attention au 's' dans _handleResponse, avec un 's' comme en anglais, je me suis fait avoir plus d'une fois...
  • on-error : la fonction à exécuter en cas d'erreur de la requête (ça peut être utile)

On peut donc remplacer <iron-ajax></iron-ajax> par :

<iron-ajax
id="sparqlRequest"
url="{{url}}"
params="{{options}}"
handle-as="application/sparql-results+json"
on-response="_handleResponse"
on-error="_handleError"></iron-ajax>



En fait, ici, nous n'avons pas défini 'url' et 'params', mais nous avons mis en face des variables Polymer ( reconnaissables avec les doubles accolades {{...}}), cela nous permet de les modifier plus facilement, donc allons y, c'est un peu plus bas, toujours dans le fichier spoggy-sparql.html, dans la fonction 'properties (){...}'

Et on va remplir les variables pour accéder à l'endpoint de Persée : 
 url avec 'http://data.persee.fr/sparql'
 et options avec la requête :  'select distinct ?p where {?p a foaf:Person} LIMIT 100 '
... voici ce qu'il faut ajouter : 


  url: {
          type: String,
          value: 'http://data.persee.fr/sparql'
        },
        options: {
          type: Object,
          value: {
            query:  'select distinct ?p where {?p a foaf:Person} LIMIT 100',
            format: 'application/sparql-results+json',
          }
        }





ETAPE 4 : LANCER LA REQUETE ET RECUPERER LES RESULTATS.


Commençons par créer un bouton pour lancer la requête : 
Comme d'habitude, on va inclure un element <paper-button> et lui mettre une fonction quand on clique dessus : 
installer le composant paper-button : 
dans 'public' executez la commande :  bower install paper-button



Puis importez-le dans notre module spoggy-sparql... 
Comme d'habitude : 
import : 

 <link rel="import" href="../../bower_components/paper-button/paper-button.html">

placement entre les balises <template>...</templates> avec le code : 

  <paper-button on-tap="executeRequete" raised> Executer la requête</paper-button>




Et ça donne quoi ??? Ben y'a qu'à aller voir sur http://127.0.0.1:8081, et actualiser la page via la touche F5, ou le bouton de rafraîchissement de page pour qu'elle se recharge



On a bien notre bouton (1) mais quand on clique dessus, on a une erreur dans la console (2): 
 listener method `executeRequete` not defined

Ah bah oui, mince, on n'a pas défini la méthode 'executeRequete' qu'on souhaite utiliser lors de l'evenement 'on-tap' du bouton (quand on clique sur le bouton)... Et bien allons-y, définissons cette méthode... son rôle ??? on a dit que quand on clique sur le bouton, on l'application devait executer la requête, non? Ok, c'est parti, attention c'est trop hard : 

Cette fois, c'est après la fonction 'properties', et il faut ajouter (voir n°3 dans l'image qui suit): 

    executeRequete(){
      this.$.sparqlRequest.generateRequest()
    }

ici, on utilise l'identifiant de notre requete 'sparqlRequest' et on lui applique la fonction 'generateRequest()' .... Tu vois... trop dur ...

Et tant qu'on y est, maintenant, je te sens chaud, allez, on se lâche, on écrit la fonction qu'on avait définie pour récupérer les résultats, rappelle-toi, c'est la fonction '_handleResponse' dans la requete <iron-ajax> (voir n°4 dans l'image qui suit)


    _handleResponse(data){
      console.log("Reponse Sparql");
      //  console.log(data);
      //  console.log(data.detail);
      //  console.log(data.detail.error);
      //  console.log(data.detail.error.message);
      //  console.log(data.detail.request);
      console.log(data.detail.response);
      let response = JSON.parse(data.detail.response);
      let link = response.head.link;
      let vars = response.head.vars;
      let results = response.results.bindings;
      console.log(results);
    }



Maintenant quand on clique sur le bouton, les résultats s'affichent dans la console, mais c'est déjà pas mal....

ETAPE 5 : AFFICHAGE DES RESULTATS SOUS FORME DE LISTE.

Mouais dans la console, ça fait pas une appli, ça, donc, on vas récupérer ces résultats, les mettre dans une variable de l'appli (this.results) et utiliser un nouveau composant Polymer (dom-repeat) pour boucler sur les resultats, agrémenté d'un autre composant, paper-item, qui est tout approprié pour afficher des listes...

Ajoutez à la fin de la fonction _handleResponse la ligne 'this.results = results'

ce qui nous donne : 
_handleResponse(data){
      console.log("Reponse Sparql");
      //  console.log(data);
      //  console.log(data.detail);
      //  console.log(data.detail.error);
      //  console.log(data.detail.error.message);
      //  console.log(data.detail.request);
      console.log(data.detail.response);
      let response = JSON.parse(data.detail.response);
      let link = response.head.link;
      let vars = response.head.vars;
      let results = response.results.bindings;
      console.log(results);
this.results = results;
    }



 Maintenant que nous avons sorti nos 'results' de la fonction et l'avons affecté à une variable du composant spoggy-sparql, 


on peut boucler dessus en ayant pris soin : 
  • d'avoir installé le composant 'paper-item'  avec la commande 'bower install paper-item' 

  • d'avoir importé les composants 'dom-repeat' et 'paper-item' (voir n°5)
<!-- import template repeater -->
<link rel="import" href="../../bower_components/polymer/lib/elements/dom-repeat.html">
<link rel="import" href="../../bower_components/paper-item/paper-item.html">


  • d'insérer le code pour boucler (voir n°6)
  <template is="dom-repeat" items="[[results]]">
      <paper-item >[[item.p.value]]</paper-item>
    </template>



Et le résultat ? 

Une liste des résultats issus de Persée....





A vous maintenant de nettoyer les résultats, les mettre en forme, changer la requête...
pourquoi pas une requête du style :
'SELECT DISTINCT ?s ?t ?sub
WHERE {
?s ?p bibo:Document.
?s dcterms:title ?t.
?s dcterms:subject ?sub.
filter (lang(?sub) = "" || langMatches(lang(?sub), "fr"))
} LIMIT 300'

???


Pour récupérer le code de ce tutoriel c'est https://github.com/scenaristeur/tutosparql


Un truc sympa, à mon humble avis, c'est de coupler avec VisJs,
Pour voir ce que ça donne, utilisez Spoggy pour vous brancher sur Persée ou DbPedia: http://spoggy.herokuapp.com/

ATTENTION utilisez l'adresse http://spoggy.herokuapp.com/ et non httpS://spoggy.herokuapp.com/  --> sans le 's' de https, car http://data.persee.fr/sparql n'est pas accessible en mode sécurisé

Voir la vidéo Quand Spoggy utilise Polymer pour s'attaquer à Persée...

Pour participer au projet Spoggy : https://github.com/scenaristeur/heroku-spoggy

Ps : tu auras peut-être remarqué que parfois que vous ai tutoyé... Normal,... dans les moments difficiles, il faut savoir être soudé...





  


















24/02/2018

Spoggy sur Heroku

test : http://spoggy.herokuapp.com/?source=https://raw.githubusercontent.com/scenaristeur/smag0-connaissance/master/Ateliers/AtelierObjetsConnectes.json


DEMO : http://spoggy.herokuapp.com/
GITHUB : https://github.com/scenaristeur/heroku-spoggy




CONSTRUCTION :

inspiré de https://devcenter.heroku.com/articles/node-websockets

```
mkdir herokuspoggy
cd herokuspoggy
npm init --yes
```

modifier package.json :
"engines": {
  "node": "6.2.0"
},
"scripts": {
  "start": "node server.js"
}

on suit l'OPTION2 : Socket.io
npm install --save --save-exact express socket.io



03/02/2018

Quand l'environnement s'adapte à moi.


Aurélien du FAbLAB... a sollicité lesBricodeurs pour un atelier à la cité du Design.
Son idée étant d'illustrer de manière concrète une des possibilités de lier les données personnelles (issues de la personne, de capteurs physiques  --> environnement)

Un capteur de rythme cardiaque qui modifierait une playlist : plus le rythme cardique est lent, plus la musique est lente et inversement.

--> Arduino Yun avec lininoio

LA LIAISON CAPTEUR APPLI-WEB

Premier code côté javascript (node, express, socket.io, librairie lininoio, avant la réception du capteur, avec un potentiomètre branché sur A0 pour simuler les variations 


       
//https://github.com/ideino/ideino-linino-lib

///////////////////////////////////////////////////////////
// GESTION NODE EXPRESS SERVEUR
///////////////////////////////////////////////////////////
var express = require('express');
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);
var port = process.env.PORT || 3006;

///////////////////////////////////////////////////////////
// GESTION DES COMMANDES LINUX comme reboot
///////////////////////////////////////////////////////////
//var exec = require('child_process').exec;

///////////////////////////////////////////////////////////
// GESTION DE LA CARTE ARDUINO
///////////////////////////////////////////////////////////
var linino = require('ideino-linino-lib'),
board = new linino.Board(),
html = new linino.Htmlboard();
//var servoH = { pin: board.pin.servo.S9, value : 180 };
//var pin13 = board.pin.digital.D13;
var pot = board.pin.analog.A0;

// Routing
app.use(express.static(__dirname + '/public'));

//LANCEMENT SERVEUR
server.listen(port, function () {
 console.log('Server listening at port %d', port);
 console.log('potentiometre : A0');
 
});




board.connect(function(){
 console.log('CARTE Connected');
 io.on('connection', function (socket) {
  console.log("CLIENT connected");
  
  
  board.analogRead(pot, function(value){
   console.log('value: ' + value);
  // var data = {"AO": value};
   socket.emit('update', value);
  });
 });
 
}); 


accompagné d'un fichier /public/index.html qui fait appel aux librairies P5JS, socket.io :
https://github.com/scenaristeur/lininoapps/blob/master/root/cardio/public/index.html


       
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0>
    <style> body {padding: 0; margin: 0;} </style>
    <script src="lib/p5.min.js"></script>
    <script src="lib/addons/p5.dom.min.js"></script>
    <script src="lib/addons/p5.sound.min.js"></script>
    <script src="js/sketch.js"></script>
  </head>
  <body>
  
  <script src="/socket.io/socket.io.js"></script>
  <script>
 var socket = io();
 console.log(socket);
 /*socket.on('update', function (data) {
  console.log(data);
 });*/
  
  </script>
  </body>
</html>


Et un fichier p5js /public/js/sketch.js :
https://github.com/scenaristeur/lininoapps/blob/master/root/cardio/public/js/sketch.js

Qui référence les chansons (sotckées dans le dossier /assets) selon leur BPM .
Comme d'hab : setup met en place l'environnement et définit le listener de la communication bi-directionnelle entre la page web et le serveur grâce au listener  'socket.on("update", function(data){...});




var bpm = 0
var play = false;
// convertisseur mp3 : http://youtubemp3.to/
// bpm search : https://songbpm.com/        https://www.bpmdatabase.com/music/search/?artist=&title=&bpm=70&genre=
// p5js playsound https://p5js.org/examples/sound-load-and-play-sound.html


var song ;
var ROAR_90, GETLUCKY_116, COME_100, WALK_105, CARMEN_150, CRUEL_168;
var slider;

function preload() {
 // soundFormats('mp3', 'ogg');
 // trover des chansons de 40 BPM à 220
  ROAR_90 = loadSound('assets/90BPM - Katy Perry - Roar Official.mp3');
 COME_100 = loadSound('assets/100BPM - Jain - Come.mp3');
 WALK_105 = loadSound('assets/105BPM - Lou Reed - Walk On The Wild Side.mp3');
 GETLUCKY_116 = loadSound('assets/116BPM - Daft Punk - Get Lucky.mp3');
 CARMEN_150 = loadSound('assets/150BPM - Stromae - carmen.mp3');
 CRUEL_168 = loadSound('assets/168BPM - Elvis Presley - Dont Be Cruel.mp3');
 console.log("chansons chargées");
}

function setup() {
 canvas = createCanvas(windowWidth, windowHeight);
 slider = createSlider(40, 220, 70);
 slider.position(10, 10);
 
 slider.style('width', width/2+'px');
 
 
 
 song = ROAR_90;
 fft = new p5.FFT();
 song.amp(0.2);
 //GETLUCKY_116.play();
 // GETLUCKY_116.pause();
 
 console.log("ready");
 console.log(socket);
   socket.on('update', function (data) {
  
  //var json = JSON.parse(data);
  
  bpm = Math.round(map(data,0,1023,40,220));
  console.log(bpm);
  slider.value(bpm);
  changeSong(ROAR_90, bpm, 80, 95);
  changeSong(COME_100, bpm, 95, 102);
  changeSong(WALK_105, bpm, 102, 110);
  changeSong(GETLUCKY_116, bpm, 110, 120);
  changeSong(CARMEN_150, bpm, 120, 160);
  changeSong(CRUEL_168, bpm, 160, 220);
 });
}

function draw() {
 var color = map(bpm,40,220,0,255);
 background(color);
 noStroke();
 //fill(255-color);
 fill(bpm, 255-bpm, 0);
 ellipse(width/2,height/2,bpm,bpm); 
 text(bpm,100,100);
 
 //visu
 var spectrum = fft.analyze();
 noStroke();
 fill(0,255,0); // spectrum is green
 for (var i = 0; i< spectrum.length; i++){
  var x = map(i, 0, spectrum.length, 0, width);
  var h = -height + map(spectrum[i], 0, 255, height, 0);
  rect(x, height, width / spectrum.length, h )
 }
 
 var waveform = fft.waveform();
 noFill();
 beginShape();
 stroke(255,0,0); // waveform is red
 strokeWeight(1);
 for (var i = 0; i< waveform.length; i++){
  var x = map(i, 0, waveform.length, 0, width);
  var y = map( waveform[i], -1, 1, 0, height);
  vertex(x,y);
 }
 endShape();
 
}


function changeSong(s, bpm, min, max){
 if ((bpm > min ) && (bpm <= max) && (song != s )){
  //ROAR_90.pause();
  song.pause();
  song = s;
  song.play()
  console.log(s);
 }
 
 
}


function mousePressed() {
 play = !play;
 console.log(play);
 if (play){
  console.log("play");
  song.play();
  
  }else{
  console.log("stop");
  song.stop();
 }
 }  


TRANSFORMATION DU SIGNAL

Une fois le capteur reçu, la difficulté était de récupéréer le rythme cardiaque . En effet, le code de base http://learn.linksprite.com/arduino/advanced-learning-kit-for-arduino/how-to-use-heatbeat-sensor-module-on-arduino/ permet de mesurer la variation d'oxygène contenue dans le sens. Il a fallu transformer le signal pour calculer une moyenne du nombre de battements par minutes.

Code de base pour Arduino :

       
// Pulse Monitor Test Script
 
int ledPin = 13;
 
int sensorPin = 0;
 
double alpha = 0.75;
 
int period = 20;
 
double change = 0.0;
 
void setup()                   
 
{
 
  pinMode(ledPin, OUTPUT);
 
  Serial.begin(115200);
 
}
 
void loop()                    
 
{
 
    static double oldValue = 0;
 
    static double oldChange = 0;
 
    int rawValue = analogRead(sensorPin);
 
    double value = alpha * oldValue + (1 - alpha) * rawValue;
 
    Serial.print(rawValue);
 
    Serial.print(",");
 
    Serial.println(value);
 
    oldValue = value;
 
    delay(period);
 
}









[ en cours de rédaction ]