Après avoir traité les données remontant de notre objet connecté, il est temps de lui envoyer des paramètres. Pour cela, AWS introduit un concept qui lui est propre, les ThingShadows.
Les ThingShadows, késako ?
Nous l’avons vu dans les articles précédents, la connexion entre le cloud et un objet n’est pas maintenue dans le temps (et heureusement pour la batterie de ce dernier !). AWS introduit donc la notion de ThingShadow, qui est une représentation virtuelle et permanente d’un objet physique. Ainsi, il est possible, à tout moment, pour un utilisateur ou un programme externe, de requêter ou de mettre à jour l’état d’un objet enregistré dans le Cloud. AWS IoT se charge de maintenir ces différents états, et proposent même deux notions, l’état souhaité (state.desired) et l’état constaté à la dernière connexion (state.reported). Ces deux états appartiennent à un document JSON. Celui-ci est consultable directement au niveau de AWS IoT, en cliquant sur votre objet.
De plus, cet état est modifiable via une API REST, dont l’url, de la forme https://
/things/
<thingName
>/shadow
se trouve sur la page Détail de votre objet. L’API respecte les règles RESTful. On peut donc récupérer la Shadow via GET, la mettre à jour via POST, et la supprimer via DELETE. Cette API est bien entendu protégée, et accessible soit via une authentification TLS mutuelle, soit via une authentification AWS S4.
Nous devrions donc pouvoir utiliser Postman pour mettre à jour l’état de notre objet connecté. Néanmoins, à notre grand regret, nous n’avons jamais réussi à utiliser l’API REST, qui répond désespérément 404. Un ticket est ouvert sur le forum. Si cet article pouvait aider à le faire avancer, nous en serions ravis.
A défaut, nous avons utilisé la CLI pour faire nos mises à jour :
aws iot-data update-thing-shadow --thing-name RaspberryPiAtelier --payload '{"state": {"desired": {"redLed": 1, "greenLed": 1, "blueLed": 1}}}' /tmp/output.json
Résultat, la Shadow est bien mise à jour :
On le constate, AWS va gérer une notion de delta entre le dernier état rapporté, et l’état souhaité. De la même façon, la mention Out of sync indique que la shadow a été mise à jour depuis la dernière connexion de l’objet.
Récupérer la shadow via le SDK NodeJs.
Dans un précédent article, nous avons utilisé l’objet thingShadows. Cet objet offre une méthode awsIot.thingShadow#register(thingName, [options] ), qui permet de s’abonner aux évènements affectant la représentation virtuelle de l’objet. Dans notre cas précis, cet abonnement n’est nécessaire que dans le cas où nous avons un downlink vers Sigfox. Nous allons donc le conditionner à la présence du flag ack(nowledge).
if (event.ack) { thingShadows.register(thingId, {ignoreDeltas: true, persistentSubscribe: true}); }
Les deux options de la méthode sont :
ignoreDeltas : AWS IoT n’enverra que la dernière shadow en date, et pas l’ensemble des représentations intermédiaires.
persistentSubscribe : permet d’accélérer la réponse de AWS IoT, au détriment de la précision des messages transmis (l’application peut potentiellement recevoir plus de « bruit »).
A noter, la méthode ne possède pas de callback, ce qui va poser problème dans la récupération de la shadow.
En effet, l’API propose une méthode awsIot.thingShadow#get(thingName, [clientToken]) qui nécessite que la fonction register soit correctement effectuée et terminée. Comme l’API ne propose pas de callback, les exemples AWS se basent sur un timer qui temporise l’exécution de get pour « s’assurer » que register est bien réalisé. Dommage de ne pas avoir respecté les règles de base de l’asynchronisme en Node, cela aurait été nettement plus classe.
if (event.ack) { setTimeout(function () { var clientToken = thingShadows.get(thingId); }, 2000); }
La réponse arrive sous forme d’évènement status, sur lequel nous allons positionner un listener. Nous en profitons pour vérifier que le résultat de l’opération que nous recevons dans ce listener est bien celui que nous avons demandé, en utilisant le clientToken. Nous nous retrouvons avec un document JSON, qui contient l’ensemble de nos DELs. Nous allons pousser ces états dans un tableau.
thingShadows .on('status', function (thingName, stat, clientTokenLocal, stateObject) { if (clientToken == clientTokenLocal) { console.log('[status][state.desired]', JSON.stringify(stateObject.state.desired)); var state = []; if (stateObject.state.desired.redLed == 1) { state.push(1); } if (stateObject.state.desired.greenLed == 1) { state.push(2); } if (stateObject.state.desired.blueLed == 1) { state.push(3); } } });
Enfin, et pour terminer, n’oublions pas que côté Sigfox, pour économiser des octets, nous avons décidé de gérer les DELs sur 2 bits. Nous allons donc convertir ce tableau en hexa, qui pourra directement être interprété au niveau de notre objet connecté. Puis nous rédigeons la response, qui respecte le formalisme du downlink Sigfox. :
var numRepr = 0 | 0; for (var i = 0; i < litLeds.length; i++) { numRepr |= (1 << litLeds[i] - 1) } var hex = ("00" + numRepr.toString(16)).slice(-2); response[event.device] = {"downlinkData": pad(hex, 16, '0')};
A ce stade, vous devriez obtenir, coté Sigfox (si vous activé le downlink), deux jolis feux verts :
Si vous avez réalisé le circuit complet, en incluant votre RaspberryPi, vous pouvez maintenant piloter vos DELs via la command line AWS !
Une intégration des certificats un peu plus pragmatique.
Les plus attentifs n’auront pas manqué de constater que lors du premier article de cette série, nous avions pris un sacré raccourci : nous avions embarqué la configuration et les certificats de notre objet directement dans notre zip Lambda. Ce qui, convenons en, est assez désagréable quand je dois gérer un nouvel objet : le code métier ne change pas, mais me voilà obligé de redéployer mon bridge Sigfox à chaque nouvel objet. Afin d’améliorer ce fonctionnement, nous vous proposons d’utiliser le service d’hébergement de fichier de AWS, S3.
Afin de gérer S3 en mode Promesses, nous allons également embarquer Bluebird.
const AWS = require('aws-sdk'); const Promise = require('bluebird'); var s3 = Promise.promisifyAll(new AWS.S3());
Nous allons utiliser S3 pour déposer, dans un bucket spécifique, un fichier de configuration par objet géré. Pour des questions de praticité, nous nommerons ces fichiers .json.
{ "keyPath": "xxx-private.pem.key", "certPath": "xxx-certificate.pem.crt", "caPath": "root-CA.crt", "clientId": "RaspberryPiAtelier", "region": "eu-west-1" }
Nous allons aussi déposer les certificats (xxx-private.pem.key et xxx-vertificate.pem.crt).
Première étape, aller récupérer la configuration de l’objet dans S3 :
var params = { Bucket: 'sigfox-bridge', Key: event.device + '.json', }; var deviceConnexionData = {}; s3.getObjectAsync(params).then(function (data) { console.log("S3 Body", data.Body.toString()); deviceConnexionData = JSON.parse(data.Body.toString()); // TO BE CONTINUED
A partir des informations de connexion, nous allons pouvoir utiliser les certificats pour nous connecter. Malheureusement, l’API IoT nécessite d’avoir les certificats en local pour pouvoir se connecter. On va donc les télécharger en local du Lamba depuis S3, en asynchrone, et en chainant les promesses.
// CONTINUED FROM PREVIOUS params = { Bucket: 'sigfox-bridge', /* required */ Key: deviceConnexionData.keyPath, /* required */ }; return s3.getObjectAsync(params) }).then(function (data) { var keyPathFile = "/tmp/" + event.device + "-private.pem.key"; deviceConnexionData.keyPath = keyPathFile; var fd = fs.openSync(keyPathFile, 'w+'); fs.writeSync(fd, data.Body, 0, data.Body.length, 0); fs.closeSync(fd); params = { Bucket: 'sigfox-bridge', /* required */ Key: deviceConnexionData.certPath, /* required */ }; return s3.getObjectAsync(params) }).then(function (data) { var certPathFile = "/tmp/" + event.device + "-certificate.pem.crt"; deviceConnexionData.certPath = certPathFile; var fd = fs.openSync(certPathFile, 'w+'); fs.writeSync(fd, data.Body, 0, data.Body.length, 0); fs.closeSync(fd); var thingShadows = awsIot.thingShadow(deviceConnexionData);
Le code est certes plus complexe, mais aussi beaucoup plus générique : notre bridge Sigfox est maintenant capable de gérer n’importe quel objet déclaré dans IoT, à partir du moment où la configuration de l’objet et ses certificats de connexions sont déposés dans le bucket S3 ad-hoc.
Le code est bien sûr disponible sur Github.
Sécuriser l’API Gateway
Dernière étape, souvent négligée, la sécurisation. Premier point, avec le code tel qu’il est écrit, AWS IoT ne peut pas recevoir de message d’un objet qui n’est pas déclaré dans son backend. Ce n’est pas le cas de tous les cloud…
Reste donc à sécuriser l’accès à l’API. Sigfox ne propose pas la génération de signature AWS version 4. Nous allons donc passer par un moyen plus basique, qui devrait néanmoins satisfaire nos besoins : l’utilisation d’une API Key. Première étape, sécuriser l’API via une key. Direction le sous-menu API Gateway, en éditant le champ API Key required :
Nous créons ensuite une API Key et nous l’associons à l’API :
Dernière étape à ne pas oublier, cliquer sur le bouton Deploy API.
Et voilà, sans l’API key, votre interface est inaccessible :
Dernière étage de la fusée, ajouter le header ad-hoc dans Sigfox. Celui-ci se nomme x-api-key :
Conclusion sur l’utilisation de AWS dans le monde de l’IoT.
Nous avons été capable de réaliser notre use-case de bout en bout et de gérer correctement notre objet connecté.
Nous allons ici tenter un résumé subjectif. Rien ne garantit que les faits énoncés dans celui-ci seront encore valables dans un mois : les briques IoT des opérateurs Cloud sont en pleine mutation, et AWS et/ou Sigfox devraient annoncer de nombreuses nouveautés dans les mois à venir.
Ce que nous avons aimé
La complétude de l’offre.
L’interface graphique qui permet d’accéder à tous les services et à leur configuration rapidement.
Les policies IoT, qui permettent de gérer des règles métier simples et d’interagir avec le reste de l’écosystème AWS.
Ce que nous avons moins aimé
L’exclusivité de MQTT pour interagir avec les objets connectés.
L’absence d’intégration AWS – Sigfox (mais nous avons open-sourcé un bridge qui réalise ce travail, profitez en !).
L’absence totale de gestion de certaines fonctions clé des objets (comme la mise à jour du firmware par exemple).
L’absence de module de dashboarding.
L’API REST qui présente un comportement étrange sur plusieurs de nos comptes.
Ce que nous n’avons pas testé faute de temps
Le scripting du déploiement d’une flotte d’objets, à la fois sur Sigfox et dans AWS IoT.
Conclusion en une image, sur notre use-case.
Et vous, avez vous testé AWS IoT ? Qu’en avez vous retenu ?