Un Sens'it, Meteor et mon Rolling Spider décolle
Comme vous l’avez peut-être lu dans mon précédent billet http://anthonnyquerouil.fr/2015/08/24/Sensit-mon-petit-objet-connecte.html, j’ai reçu récemment un Sens’it.
J’ai aussi depuis peu un Rolling-spider que je m’étais procuré dans le but de le controller avec un peu de code JS (c’est à cause de @k33g_org tout ça). L’heure est venue de mixer le tout !
L’objectif
L’objectif est plutôt simple, double cliquer sur le Sens’it et faire décoller le Rolling-spider.
Pour ce faire, voici ce qu’il nous faut :
-
Un Sens’it,
-
Un Parrot Rolling Spider,
-
Un framework JS permettant de controller le spider (on utilisera node-rolling-spider wrapper dans un package meteor anthonny:rolling-spider),
-
Un peu de Meteor pour lier le tout.
L’architecture
-
Le Sens’it va transmettre un message sur le réseau SIGFOX,
-
Les serveurs SIGFOX vont communiquer aux serveurs AXIBLE qu’un message button a été envoyé,
-
AXIBLE va appelé l’URL de Callback que nous aurons défini dans l’interface d’administration,
-
Ce callback pointera sur une application sensit.meteor.com que nous aurons déployée au préalable,
-
Cette dernière enregistrera une trace de l’appel dans une collection Mongo,
-
Notre application cliente établira une connexion DDP avec l’application sensit.meteor.com, et observera les changements effectués sur cette collection,
-
Au premier changement détecté, on décolle.
La remote-app : sensit.meteor.com
Définition de l’URL de callback
Dans l’interface Sens’it, nous allons spécifier une URL de callback pour les notifications de type button.
Pour rappel, ce callback est appelé via un GET et peut recevoir en query param certaines valeurs (via des variables {{my_var}}) :
-
device_id
-
device_serial_number
-
sensor_id
-
mode
-
notification_type
-
data
-
date
Nous pouvons donc définir l’URL suivante :
http://sensit.meteor.com/sensit-callback/button?device_id={{device_id}}&device_serial_number={{device_serial_number}}&sensor_id={{sensor_id}}&mode={{mode}}¬ification_type={{notification_type}}&data={{data}}&date={{date}}
L’application Meteor
Nous créons notre application meteor :
meteor create remote-app
cd remote-app
meteor remove autopublish insecure
rm remote-app.*
mkdir server
touch server/main.js
Pour la gestion des routes, on utilisera iron-router :
meteor add iron:router
Commençons par créer une collection notification
qui aura pour but de stocker les différentes notifications reçues.
Notification = new Meteor.Collection("notification");
Il faut ensuite définir notre endpoint qui répondra à l’appel du callback.
Router.route('/sensit-callback/:type', {where: 'server'})
.get(function () {
if (['temperature', 'motion', 'button'].indexOf(this.params.type) < 0)
throw new Error('Invalid type');
var notification = _.extend({type: this.params.type}, this.params.query, {data: JSON.parse(this.params.query.data)});
Notification.insert(notification);
this.response.end('notification ' + notification.type + ' saved\n');
});
Nous définissons une route /sensit-callback/:type
dans laquelle :type
peut prendre une des valeurs suivantes ['temperature', 'motion', 'button']
.
On construit ensuite un objet notification
à partir du type
et des queryParam (on force la conversion en JSON du paramètre data
car c’est un objet JSON stringifié).
Enfin on insère notre notification
en base et on répond en confirmant l’enregistrement.
Pour que les informations présentes dans notre collection soient accessibles par notre "client", il faut les publier (au passage, on va gérer les autres types) :
Meteor.publish("remote-button", function (argument) {
return Notification.find({type: 'button'});
});
Meteor.publish("remote-temperature", function (argument) {
return Notification.find({type: 'temperature'});
});
Meteor.publish("remote-motion", function (argument) {
return Notification.find({type: 'motion'});
});
Le code complet :
Notification = new Meteor.Collection("notification");
Router.route('/sensit-callback/:type', {where: 'server'})
.get(function () {
if (['temperature', 'motion', 'button'].indexOf(this.params.type) < 0)
throw new Error('Invalid type');
var notification = _.extend({type: this.params.type}, this.params.query, {data: JSON.parse(this.params.query.data)});
Notification.insert(notification);
this.response.end('notification ' + notification.type + ' saved\n');
});
Meteor.publish("remote-temperature", function (argument) {
return Notification.find({type: 'temperature'});
});
Meteor.publish("remote-motion", function (argument) {
return Notification.find({type: 'motion'});
});
Meteor.publish("remote-button", function (argument) {
return Notification.find({type: 'button'});
});
Un peu de test
On démarre l’application :
meteor
On requête l’url :
http://localhost:3000/sensit-callback/button?device_serial_number=ABCDE¬ification_type=generic_punctual&data=%7B%22first_name%22%3A%22Anthonny%22%2C%22sensit_name%22%3A%22%22%2C%22last_name%22%3A%22Querouil%22%2C%22device_id%22%3A%22ABCDE%22%7D&device_id=1234&sensor_id=5678&date=2015-09-01T17%3A37Z&mode=6
Le service répond correctement, et notre notification
est bien enregistrée :
Le déploiement
L’application sera déployée sur l’URL sensit.meteor.com :
meteor deploy sensit.meteor.com
Pour valider le bon déploiement, on peut reprendre le test effectué au préalable et le faire pointer sur notre "production" :
http://sensit.meteor.com/sensit-callback/button?device_serial_number=ABCDE¬ification_type=generic_punctual&data=%7B%22first_name%22%3A%22Anthonny%22%2C%22sensit_name%22%3A%22%22%2C%22last_name%22%3A%22Querouil%22%2C%22device_id%22%3A%22ABCDE%22%7D&device_id=1234&sensor_id=5678&date=2015-09-01T17%3A37Z&mode=6
Enfin, on vérifie que la notification
est bien présente en base :
La local-app : sensit-meteor-rs
Nous avons désormais un backend qui prend en compte les différentes notifications, il nous faut maintenant une application qui tournera localement et qui réagira aux changements qui surviennent dans le backend.
L’application
meteor create local-app
cd local-app
meteor remove autopublish insecure
rm local-app.*
mkdir server
touch server/main.js
Nous allons initier une connexion DDP avec notre backend et écouter les changements qui sont faits sur la collection notification
.
Pour chaque notification ajoutée dans cette collection que nous appellerons RemoteNotification
, nous ajouterons une copie dans notre collection locale Notification
:
// Déclaration de la connexion
var remote = DDP.connect('http://sensit.meteor.com/');
var RemoteNotification = new Meteor.Collection('notification', { connection: remote });
remote.subscribe('remote-button');
// On écoute les changements effectués sur la collection en Remote
RemoteNotification.find().observe({
added: function(notification) {
console.log('-- remote item added --');
// On upsert dans la collection de Notification locale
Notification.upsert({notification._id}, {$set: notification});
}
});
Il ne nous reste plus qu’à faire décoller le spider lorsqu’une notification
est ajoutée en local :
var rollingSpider = new RollingSpider();
rollingSpider.connect(Meteor.bindEnvironment(function () {
rollingSpider.setup(Meteor.bindEnvironment(function () {
rollingSpider.flatTrim();
rollingSpider.startPing();
rollingSpider.flatTrim();
// On observe la collection Notification, au premier ajout on decolle !
Notification.find().observe({
added: function (notification) {
rollingSpider.takeOff();
rollingSpider.flatTrim();
}
});
}));
}));
Le code complet :
var Notification = new Meteor.Collection("notification");
var remote = DDP.connect('http://sensit.meteor.com/');
var RemoteNotification = new Meteor.Collection('notification', { connection: remote });
var isFlying = false;
RemoteNotification.find().observe({
added: function(notification) {
console.log('-- remote item --');
console.log(notification);
Notification.upsert({_id: notification._id}, {$set: notification});
}
});
remote.subscribe('remote-button');
rollingSpider.connect(Meteor.bindEnvironment(function () {
rollingSpider.setup(Meteor.bindEnvironment(function () {
rollingSpider.flatTrim();
rollingSpider.startPing();
rollingSpider.flatTrim();
Notification.find().observe({
added: function (notification) {
if (!isFlying) {
isFlying = true;
rollingSpider.takeOff();
rollingSpider.flatTrim();
}
}
});
}));
}));
Décollage !
Conclusion
Ce billet est l’occasion de mettre en avant la connexion entre deux applications Meteor via le protocole DDP et de vous montrer qu’avec du javascript, on se marre bien (en tout cas, c’est vrai pour moi :) ).
Si vous avez des projets similaires, n’hésitez pas à m’en faire part, ce sera un plaisir d’échanger dessus.