Un article rapide pour présenter une vidéo qui donne vie aux différents sujets que j'ai pu aborder dans mes précédents articles. Enjoy!
Connecter un automate à un serveur OPC/UA sous Linux
J'ai déjà présenté dans de précédents articles le couteau suisse de la connectivité OT/IT : KEPServerEX. Indispensable pour exposer les données d'une grande variété de machines via un serveur OPC/UA. Il est prévu pour fonctionner sous un environnement Windows.
Nous allons voir ici comment utiliser son pendant pour serveurs Linux : ThingWorx Kepware Edge.
Pour les besoins de cet article, nous utiliserons de nouveau notre PLC Siemens Logo! 8.
Contrairement à la version pour Windows, celle-ci ne dispose pas d'interface graphique. En revanche, elle expose une interface REST très complète et très bien documentée dans le ThingWorx Kepware Edge User Manual disponible en téléchargement. Elle est même autodocumentée, via des APIs spécifiques. Par exemple :
Définition des drivers/channels spécifiques
La requête suivante renvoie les éléments nécessaires à la création de channels dédiés au type de machine/équipement à connecter :
GET https://localhost:57513/config/v1/doc/drivers/Siemens%20TCP%2FIP%20Ethernet/channels
Réponse :
{
"type_definition": {
"name": "Channel",
"collection": "channels",
"namespace": "servermain",
"can_create": true,
"can_delete": true,
"can_modify": true,
"auto_generated": false,
"requires_driver": true,
"access_controlled": true,
"child_collections": [
"devices",
"phonebooks"
]
},
"property_definitions": [
{
"symbolic_name": "common.ALLTYPES_NAME",
"display_name": "Name",
"display_description": "Specify the identity of this object.",
"read_only": false,
"type": "String",
"default_value": null,
"minimum_length": 1,
"maximum_length": 256
},
{
"symbolic_name": "common.ALLTYPES_DESCRIPTION",
"display_name": "Description",
"display_description": "Provide a brief summary of this object or its use.",
"read_only": false,
"type": "String",
"default_value": null,
"minimum_length": 0,
"maximum_length": 255
},
{
"symbolic_name": "servermain.MULTIPLE_TYPES_DEVICE_DRIVER",
"display_name": "Driver",
"display_description": "",
"read_only": true,
"type": "String",
"default_value": "Siemens TCP/IP Ethernet",
"minimum_length": 0,
"maximum_length": -1
},
{
"symbolic_name": "servermain.CHANNEL_DIAGNOSTICS_CAPTURE",
"display_name": "Diagnostics Capture",
"display_description": "Select whether or not to make the channel's diagnostic information available to an OPC application.",
"read_only": false,
"type": "EnableDisable",
"default_value": false
},
{
"symbolic_name": "servermain.CHANNEL_UNIQUE_ID",
"display_name": "Unique Id",
"display_description": "Unique identifier associated with this object.",
"read_only": false,
"type": "Integer",
"default_value": 422613010,
"minimum_value": null,
"maximum_value": null
},
{
"symbolic_name": "servermain.CHANNEL_ETHERNET_COMMUNICATIONS_NETWORK_ADAPTER_STRING",
"display_name": "Network Adapter",
"display_description": "Specify the name of a network adapter to bind or allow the OS to select the default.",
"read_only": false,
"type": "StringWithBrowser",
"default_value": null,
"minimum_length": 0,
"maximum_length": -1,
"hints": [
"",
"wlp3s0",
"eno1"
]
},
...
]
}
Définition des devices
La requête suivante renvoie les éléments nécessaires à la création de devices dédiés au type de machine/équipement à connecter :
GET https://localhost:57513/config/v1/doc/drivers/Siemens%20TCP%2FIP%20Ethernet/devices
Réponse :
{
"type_definition": {
"name": "Device",
"collection": "devices",
"namespace": "servermain",
"can_create": true,
"can_delete": true,
"can_modify": true,
"auto_generated": false,
"requires_driver": true,
"access_controlled": true,
"child_collections": [
"services",
"tag_groups",
"tags"
]
},
"property_definitions": [
{
"symbolic_name": "common.ALLTYPES_NAME",
"display_name": "Name",
"display_description": "Specify the identity of this object.",
"read_only": false,
"type": "String",
"default_value": null,
"minimum_length": 1,
"maximum_length": 256
},
{
"symbolic_name": "common.ALLTYPES_DESCRIPTION",
"display_name": "Description",
"display_description": "Provide a brief summary of this object or its use.",
"read_only": false,
"type": "String",
"default_value": null,
"minimum_length": 0,
"maximum_length": 255
},
{
"symbolic_name": "servermain.MULTIPLE_TYPES_DEVICE_DRIVER",
"display_name": "Driver",
"display_description": "",
"read_only": true,
"type": "String",
"default_value": "Siemens TCP/IP Ethernet",
"minimum_length": 0,
"maximum_length": -1
},
{
"symbolic_name": "servermain.DEVICE_MODEL",
"display_name": "Model",
"display_description": "Select the specific type of device associated with this ID. Options depend on the type of communications in use.",
"read_only": false,
"type": "Enumeration",
"default_value": null,
"enumeration": {
"S7-200": 0,
"S7-300": 1,
"S7-400": 2,
"S7-1200": 3,
"S7-1500": 4,
"NetLink: S7-300": 5,
"NetLink: S7-400": 6
}
},
{
"symbolic_name": "servermain.DEVICE_UNIQUE_ID",
"display_name": "Unique Id",
"display_description": "Unique identifier associated with this object.",
"read_only": false,
"type": "Integer",
"default_value": 4145021575,
"minimum_value": null,
"maximum_value": null
},
{
"symbolic_name": "servermain.DEVICE_ID_FORMAT",
"display_name": "ID Format",
"display_description": "Indicate the format of the device ID (set by the driver by default).",
"read_only": false,
"type": "Enumeration",
"default_value": 0,
"enumeration": {
"Octal": 0,
"Decimal": 1,
"Hex": 2
}
},
...
]
}
Munissez-vous donc d'un client d'API comme Postman, d'un client OPC/UA comme UAExpert, et c'est parti!
Les endpoints
La documentation ThingWorx Kepware Edge Quick Start décrit les étapes nécessaires pour connecter un premier client UAExpert au serveur OPC/UA, via le endpoint créé par défaut par l'installation standard. Ce dernier est sécurisé et requiert une validation manuelle pour accepter la connexion du client au serveur.
Mais il est fréquent que le client qui sera utilisé dans une application (que vous découvrirez dans un prochain article ;) ne supporte pas ce mécanisme d'authentification. Il devient donc nécessaire de créer en premier lieu un nouveau endpoint "non sécurisé".
Ceci peut donc être fait via l'API REST de Kepware Edge :
POST https://localhost:57513/config/v1/admin/ua_endpoints
Body :
{
"common.ALLTYPES_NAME": "UnsecureEndpoint",
"common.ALLTYPES_DESCRIPTION": "Available adapters: Default; wlp3s0:192.168.1.94; vboxnet0:192.168.56.1; eno1:; localhost",
"libadminsettings.UACONFIGMANAGER_ENDPOINT_ENABLE": true,
"libadminsettings.UACONFIGMANAGER_ENDPOINT_ADAPTER": "Default",
"libadminsettings.UACONFIGMANAGER_ENDPOINT_PORT": 49331,
"libadminsettings.UACONFIGMANAGER_ENDPOINT_URL": "opc.tcp://localhost:49331",
"libadminsettings.UACONFIGMANAGER_ENDPOINT_SECURITY_NONE": true,
"libadminsettings.UACONFIGMANAGER_ENDPOINT_SECURITY_BASIC128_RSA15": 0,
"libadminsettings.UACONFIGMANAGER_ENDPOINT_SECURITY_BASIC256": 0,
"libadminsettings.UACONFIGMANAGER_ENDPOINT_SECURITY_BASIC256_SHA256": 0
}
Il sera nécessaire de redémarrer le serveur pour prendre en compte cette modification. Ce nouveau endpoint apparaitra donc dans le client OPC/UA :
Le Channel
En s'appuyant sur la documentation récupérée plus haut, nous pouvons créer le channel spécifique à notre PLC Siemens :
POST https://localhost:57513/config/v1/project/channels
Body :
{
"common.ALLTYPES_NAME": "SiemensChannel",
"servermain.MULTIPLE_TYPES_DEVICE_DRIVER": "Siemens TCP/IP Ethernet",
"servermain.CHANNEL_DIAGNOSTICS_CAPTURE": false,
"servermain.CHANNEL_WRITE_OPTIMIZATIONS_METHOD": 2,
"servermain.CHANNEL_WRITE_OPTIMIZATIONS_DUTY_CYCLE": 10,
"servermain.CHANNEL_NON_NORMALIZED_FLOATING_POINT_HANDLING": 0,
"servermain.CHANNEL_ETHERNET_COMMUNICATIONS_NETWORK_ADAPTER_STRING":"wlp3s0"
}
Le Device
Nous pouvons maintenant associer à notre Channel un Device représentant notre Logo! 8 :
POST https://localhost:57513/config/v1/project/channels/SiemensChannel/devices
Body :
{
"common.ALLTYPES_NAME": "Logo8",
"servermain.DEVICE_ID_STRING": "192.168.1.13",
"servermain.MULTIPLE_TYPES_DEVICE_DRIVER": "Siemens TCP/IP Ethernet",
"servermain.DEVICE_MODEL": 0,
"servermain.DEVICE_ID_FORMAT": 0,
"servermain.DEVICE_ID_HEXADECIMAL": 0,
"servermain.DEVICE_ID_DECIMAL": 0,
"servermain.DEVICE_ID_OCTAL": 0,
"servermain.DEVICE_DATA_COLLECTION": true,
"servermain.DEVICE_SIMULATED": false,
"servermain.DEVICE_SCAN_MODE": 0,
"servermain.DEVICE_SCAN_MODE_RATE_MS": 1000,
"servermain.DEVICE_SCAN_MODE_PROVIDE_INITIAL_UPDATES_FROM_CACHE": false,
"servermain.DEVICE_CONNECTION_TIMEOUT_SECONDS": 3,
"servermain.DEVICE_REQUEST_TIMEOUT_MILLISECONDS": 2000,
"servermain.DEVICE_RETRY_ATTEMPTS": 2,
"servermain.DEVICE_INTER_REQUEST_DELAY_MILLISECONDS": 0,
"servermain.DEVICE_AUTO_DEMOTION_ENABLE_ON_COMMUNICATIONS_FAILURES": false,
"servermain.DEVICE_AUTO_DEMOTION_DEMOTE_AFTER_SUCCESSIVE_TIMEOUTS": 3,
"servermain.DEVICE_AUTO_DEMOTION_PERIOD_MS": 10000,
"servermain.DEVICE_AUTO_DEMOTION_DISCARD_WRITES": false,
"servermain.DEVICE_TAG_GENERATION_ON_STARTUP": 1,
"servermain.DEVICE_TAG_GENERATION_DUPLICATE_HANDLING": 0,
"servermain.DEVICE_TAG_GENERATION_GROUP": "",
"servermain.DEVICE_TAG_GENERATION_ALLOW_SUB_GROUPS": true,
"siemens_tcpip_ethernet.DEVICE_COMMUNICATIONS_PORT_NUMBER": 102,
"siemens_tcpip_ethernet.DEVICE_COMMUNICATIONS_MPI_ID": 0,
"siemens_tcpip_ethernet.DEVICE_S7_COMMUNICATIONS_MAX_PDU": 960,
"siemens_tcpip_ethernet.DEVICE_S7_COMMUNICATIONS_200_LOCAL_TSAP": 200,
"siemens_tcpip_ethernet.DEVICE_S7_COMMUNICATIONS_200_REMOTE_TSAP": 200,
"siemens_tcpip_ethernet.DEVICE_S7_COMMUNICATIONS_300_400_1200_1500_LINK_TYPE": 3,
"siemens_tcpip_ethernet.DEVICE_S7_COMMUNICATIONS_CPU_RACK": 0,
"siemens_tcpip_ethernet.DEVICE_S7_COMMUNICATIONS_CPU_SLOT": 2,
"siemens_tcpip_ethernet.DEVICE_ADDRESSING_BYTE_ORDER": 0,
"siemens_tcpip_ethernet.DEVICE_TAG_IMPORT_TYPE": 0,
"siemens_tcpip_ethernet.DEVICE_TAG_IMPORT_CODE_PAGE": 4294967295,
"siemens_tcpip_ethernet.DEVICE_TAG_IMPORT_STEP_7_PROJECT_FILE": "",
"siemens_tcpip_ethernet.DEVICE_TAG_IMPORT_PROGRAM_PATH": "",
"siemens_tcpip_ethernet.DEVICE_TAG_IMPORT_TIA_EXPORT_FILE": ""
}
Toutes ces informations correspondent à ce qui est demandé par l'assistant de configuration dans la version Windows.
Les Tags
Terminons par le plus important, exposer les informations de l'automate via les tags OPC/UA :
POST https://localhost:57513/config/v1/project/channels/SiemensChannel/devices/Logo8/tags
Body :
[
{
"common.ALLTYPES_NAME": "Entree1",
"servermain.TAG_ADDRESS": "I0.0",
"servermain.TAG_DATA_TYPE": 1,
"servermain.TAG_READ_WRITE_ACCESS": 1,
"servermain.TAG_SCAN_RATE_MILLISECONDS": 100,
"servermain.TAG_SCALING_TYPE": 0
},
{
"common.ALLTYPES_NAME": "EntreeReseau1",
"servermain.TAG_ADDRESS": "V0.0",
"servermain.TAG_DATA_TYPE": 1,
"servermain.TAG_READ_WRITE_ACCESS": 1,
"servermain.TAG_SCAN_RATE_MILLISECONDS": 100,
"servermain.TAG_SCALING_TYPE": 0
},
{
"common.ALLTYPES_NAME": "Output1",
"servermain.TAG_ADDRESS": "Q0.0",
"servermain.TAG_DATA_TYPE": 1,
"servermain.TAG_READ_WRITE_ACCESS": 1,
"servermain.TAG_SCAN_RATE_MILLISECONDS": 100,
"servermain.TAG_SCALING_TYPE": 0
}
]
Et voilà! Notre serveur est prêt à être utilisé :
Pour aller plus loin
Si vous avez besoin de configuration spécifique, avec des attributs particuliers, pour ne pas avoir à fouiller dans toute la documentation, il est possible d'exporter un projet déjà existant sous Kepware version Windows au format JSON : vous retrouverez ainsi tout le contenu nécessaire pour appeler les APIs REST!
Récupération des logs
Les logs du serveur, habituellement visibles sous Windows dans la console, peuvent être récupérés via cette interface :
GET https://localhost:57513/config/v1/event_log
Fichier(s) joint(s) :
Créer un réseau LoRa et exploiter les données transmises
Nous allons voir ici comment contruire facilement votre propre réseau local LoRaWAN.
Le matériel
Avant de commencer, il est nécessaire de s'équiper d'un ou plusieurs devices constituant les noeuds finaux (transmettant les données de capteurs) ainsi que d'une passerelle (gateway) pour la centralisation des messages. Pour plus de détails sur l'architecture LoRaWAN, vous pouvez voir ici.
Pour cet article, la plateforme qui recevra nos messages LoRa (le Network server) sera The Things Network (TTN) : gratuit, complet et simple d'utilisation.
A ce jour, le matériel le plus abordable (tant en prix qu'en complexité) sur le marché est fourni par Pycom : en premier lieu leur toute nouvelle passerelle Pygate, qui est très simple à prendre en main et peu chère. Elle est programmable en MicroPython et dispose d'une extension Ethernet/PoE. Pour ce qui est des noeuds et capteurs, un LoPy4 posé sur une carte d'extensions PySense répondra à la plupart des besoins.
Je passe sur les détails de l'assemblage des cartes, très bien expliqué sur leur site.
La programmation
Le développement en MicroPython et la programmation des cartes peut se faire assez simplement avec VSCode et l'extension Pymakr.
La passerelle
Avant même de commencer à programmer la Pygate, il est nécessaire de l'enregistrer auprès de la plateforme TTN afin de récupérer des indentifiants. Pour cela, il suffit de suivre les instructions ici.
Ensuite, sur le site de Pycom se trouve un très bon exemple de code permettant de programmer la passerelle pour se connecter à TTN via Wifi. Si vous êtes équipés du module Ethernet/PoE, il suffit de remplacer le code intialisant l'interface Wifi par celui-ci :
from network import ETH
eth = ETH()
while not eth.isconnected():
print("connecting to ethernet")
time.sleep(1)
print("ifconfig", eth.ifconfig())
Ainsi, une fois la gateway connectée à un réseau accédant à Internet, la communication s'établira toute seule et vous verrez remonter régulièrement des signaux de vie sur la page de TTN.
Les noeuds
Pour ce qui est du code nécessaire à l'activation d'un end device, il existe encore une fois un très bon exemple sur le site de Pycom. Comme expliqué, il vous sera nécessaire de créer une application dans TTN pour récupérer tous les identifiants utiles.
Afin de transmettre les données des capteurs d'un Pysense, tout est disponible sur cette page.
Le décodage des données
Sur TTN, le payload des messages est encodé en hexadécimal, sous forme d'octets. Mais la plaforme met à disposition, au sein des Applications, des moyens de décoder et manipuler les contenus afin de faciliter leur utilisation, et ce à partir de fonctions Javascript.
Le Decoder
Ces fonctions permettent de transformer le contenu brut (octets) en données lisibles.
Prenons le scénario suivant : le payload envoyé est constitué de données encodées selon un mécanisme personnalisé : 15 premiers bits pour une température, 7 bits suivants pour une humidité... Cette succession de bits est finalement encodée en hexa afin d'être transmise.
Voici un exemple de décodeur qui transforme le payload des messages LoRa en une chaine binaire :
function Decoder(bytes, port) {
function bytesToBinStr(bytes) {
var binstr = '';
for(var i=0; i<bytes.length; i++) {
var str = bytes[i].toString(2);
while(str.length<8){
str = '0'+str;
}
binstr += str;
}
return binstr;
}
}
Ensuite, selon notre scénario, une fonction permettant de reconstituer la température :
function parseForTemperature(binStr) {
var level = 0.0;
if (binStr.length >= 18) {
var entier = parseInt(binStr.substr(3, 7), 2);
var dec = parseInt(binStr.substr(10, 8), 2);
level = entier + (dec / 100);
if(binStr.substr(2, 1)=="1") {
level = level * -1;
}
}
return level;
}
Enfin, le code complet du Decoder permettant de retourner la température :
function Decoder(bytes, port) {
function bytesToBinStr(bytes) {
...
}
function parseForTemperature(binStr) {
...
}
// Decode an uplink message from a buffer
// (array) of bytes to an object of fields.
var decoded = {};
var binstr = bytesToBinStr(bytes);
decoded.temp = parseForTemperature(binstr);
return decoded;
}
Le Converter
Imaginons maintenant que nous souhaitions transmettre ces données à une plateforme tierce. Il est possible grâce aux convertisseurs de transformer les données décodées afin d'en adapter le format, pour être correctement interprétées par la plateforme cible (ceux qui me suivent reconnaitront Cumulocity ;). En voici un exemple :
function Converter(decoded, port) {
// Merge, split or otherwise
// mutate decoded fields.
var date = new Date().toISOString();
var tempMeasurement = {'c8y_TemperatureMeasurement':{'T':{'unit':'C','value':decoded.temp}}};
tempMeasurement.type = 'c8y_TemperatureMeasurement';
tempMeasurement.time = date;
tempMeasurement.source = {'id':'3466704'};
var converted = {'measurements':[tempMeasurement]};
return converted;
}
Ce qui, ici, produira au final un message de la forme :
{
"app_id": "pygate-test",
"dev_id": "pysense",
"hardware_serial": "...",
"port": 2,
"counter": 27,
"payload_raw": "CBHMmQFHhVM=",
"payload_fields": {
"measurements": [
{
"c8y_TemperatureMeasurement": {
"T": {
"unit": "C",
"value": 32.71
}
},
"source": {
"id": "3466704"
},
"time": "2020-10-27T16:21:50.698Z",
"type": "c8y_TemperatureMeasurement"
}
]
},
"metadata": {
"time": "2020-10-27T16:21:50.491459008Z",
"frequency": 868.1,
"modulation": "LORA",
"data_rate": "SF7BW125",
"coding_rate": "4/5",
"gateways": [
{
"gtw_id": "eui-...",
"timestamp": 2919800980,
"time": "",
"channel": 0,
"rssi": -25,
"snr": 10,
"rf_chain": 0
}
]
},
"downlink_url": "https://integrations.thethingsnetwork.org/ttn-eu/api/v2/down/.../integromat?key=..."
}
Vous avez maintenant tout ce qu'il vous faut pour monter votre propre réseau LoRa et faire voyager vos données!
Nous verrons dans un prochain article comment développer un microservice sur Cumulocity pour récupérer ces messages et en extraire les measurements utiles.
Fichier(s) joint(s) :
Pilotez votre usine pour une production plus agile grâce au Edge computing
Pour faire suite à mon précédent article, où nous avions vu comment lier les réseaux IT et OT pour faire de la supervision (par exemple d'une chaine de production), il reste maintenant à voir comment agir sur les équipements connectés dans l'usine : envoyer des commandes, définir des programmes, déclencher des arrêts...
Pour simplifier la réalisation et ne pas interférer avec les réseaux de terrains spécifiques (Profinet, Profibus, AS-i...), nous allons interfacer le noeud "de plus haut niveau" en termes de réseaux OT : l'automate!
Voici en résumé ce qui peut être mis en place :
En quelques mots :
- La plateforme IoT Cumulocity Edge permet de planifier n'importe quel type d'opération à destination d'un équipement spécifique (marche/arrêt, changement de programme...)
- Une instance de Kepware Kepserver permet d'exposer les données et les actions possible sur un équipement (quel qu'il soit, même ancien et/ou propriétaire) via une interface OPC/UA
- Un "agent" permet de récupérer les opérations en attente sur la plateforme pour les interpréter en appels de méthodes OPC/UA, que Kepware traduira ensuite en langage automate
Le pilotage du parc machines grâce aux fonctions de device management
Pour commencer, les commandes à envoyer aux équipements sont décrites dans des "opérations" sur la plateforme IoT :
Le principe est le suivant : l'opération est créée pour un équipement spécifique, au statut "En attente". L'équipement (ou un agent) va alors consulter régulièrement la plateforme pour savoir si une opération lui a été demandée. Le cas échéant, il la prendra en considération en la passant au statut "En cours d'exécution" et réalisera la tâche demandée. Une fois terminée, il passera l'opération au statut "Succès" ou "Echec" selon le résultat.
Un Agent pour orchestrer les opérations
Dans certains cas, l'intégration d'un agent spécifique pourra être nécessaire pour scruter le contenu de la plateforme IoT et piloter en conséquence les interfaces OPC/UA existantes.
Un des moyens les plus versatiles pour ce genre de besoin peut être un script en langage Python : il peut facilement s'intégrer avec les interfaces de la plateforme IoT et l'OPC/UA et peut être déployé sur n'importe quelle passerelle ou autre équipement réseau de ce type.
Vous voilà équipés pour interagir au mieux avec vos lignes de production!
Fichier(s) joint(s) :
Convergence des réseaux OT et IT en pratique : de l'automate à la plateforme IoT (sans Cloud!)
Après avoir traité le sujet du Edge computing dans mon précédent article, il est temps d'aborder un autre frein souvent recontré lors de la transformation numérique d'une unité de production : l'unification des réseaux industriels (OT) et applicatifs (IT).
L'automate comme noeud central
Les APIs (Automate programmable industriel) ou PLC en anglais (Programmable Logic Controller) sont depuis longtemps les "cerveaux" des usines, orchestrant les différents processus techniques. Depuis quelques années, les fabricants misent de plus en plus sur leur rôle central en les rendant de plus en plus intelligents et interconnectés.
Mais avant d'envisager le remplacer de tout le parc installé dans un site, il existe différents moyens permettant de faire remonter les informations d'un automate standard (même ancien) vers des applications du SI.
Cas d'usage
Le PoC réalisé ici repose sur un des automates les plus répandus dans l'industrie, à savoir un Siemens S7-200, décliné dans le modèle d'entrée de gamme le LOGO! 8. Plusieurs briques logicielles vont lui être adossées afin de récupérer les états de ses différentes entrées/sorties et les remonter vers la plateforme IoT Cumulocity Edge installée localement.
Kepware Kepserver comme porte d'entrée
Ce logiciel distribué par PTC est le véritable couteau suisse de l'industrie 4.0, car il permet de se connecter directement et nativement à un grand nombre d'automates puisqu'il contient tous les pilotes nécessaires :
Après configuration pour utiliser le pilote Siemens et reconnaissance des entrées/sorties, il expose automatiquement les données issues de l'automate via un serveur OPC/UA :
Cumulocity Edge et client OPC/UA
La version Edge de Cumulocity IoT embarque nativement un connecteur vers un serveur OPC/UA. Il suffit donc de le faire pointer vers le serveur exposé par Kepware pour récupérer les informations de l'automate :
Vous voici entrés dans la 4ème révolution industrielle!
Fichier(s) joint(s) :
IoT Edge computing pour la maintenance conditionnelle
La transformation vers l'"Industrie 4.0" implique de profonds changements pour les usines (organisationnels, technologiques...). Parmi eux, le concept de "Edge computing" fait partie des plus attendus et des plus complexes. Il découle du besoin grandissant d'analyser de plus en plus d'informations issues des machines, dans le contexte notamment de la maintenance conditionnelle, qui permet de connaitre en temps réel leur état pendant les phases de production.
Vers l’usine intelligente...
Le "Edge computing" a pour vocation d'apporter des capacités de calcul et de prise de décision au sein même de l'usine, au plus près des machines.
L'intérêt premier est de répondre aux problématiques de sécurité, vis-à-vis de la connectivité avec un Cloud : disposer d'une plateforme IoT fonctionnant entièrement en mode déconnecté permet de s'affranchir de toutes les difficultés d'ouvrir un réseau OT vers l'extérieur et ainsi limiter les risques de failles de sécurité.
Le second intérêt est de permettre l'analyse en temps réel de signaux complexes et volumineux, tels que des vibrations ou des rotations et ainsi accélérer la prise de décision. Plus besoin d'agréger ou préformatter les données avant de les envoyer vers un Cloud, ce qui réduit les efforts nécessaires et améliore la qualité des informations utilisées dans les analyses.
... utilisant l'expérience de l'Homme
Mais cette usine "agile" et "intelligente" ne serait rien sans le savoir et le savoir-faire des personnels de maintenance. Leur connaissance du parc de machines et leur expérience en détection de pannes sont les éléments clés qui doivent être utilisés et préservés tout au long du processus de transformation numérique.
Il est donc important que les nouveaux outils introduits au sein de l'usine soient au service de ces Hommes et leur permettent de garder la main sur leur métier, tout en valorisant leurs connaissances et en facilitant leur quotidien.
Par des industriels, pour des industriels
Brilliant things repose sur la plateforme Cumulocity IoT, de l'éditeur allemand SoftwareAG, plusieurs fois reconnue comme leader sur le marché par des organismes observateurs indépendants. Celle-ci apporte toutes les fonctionnalités d'une plateforme (dashboard, device management, droits d'accès, stockage, sécurité...) ainsi qu'une large palette de connecteurs vers des protocoles de communication industriels (OPC/UA, Modbus...) et des pilotes pour des machines de grands fabricants (DMG-MORI, Dürr, Engel...) développés par le consensus d’industriels ADAMOS.
De plus, sa version Edge décline toutes les fonctionnalités de la version SaaS dans un environnement pouvant être déployé localement, sur un serveur situé dans l'usine, sans nécessiter de connexion vers l'internet. Elle offre en supplément la présence du moteur de streaming analytics Apama, capable de recevoir et traiter d'importants flux d'évènements.
Plus de pouvoir pour la maintenance
Brilliant things est une solution destinée au personnel de maintenance : elle met à disposition une interface simple d'utilisation pour créer des corrélations d'évènements, afin de générer une information à haute valeur pour la maintenance, à partir d'évènements disparates reçus des différents équipements de l'usine.
Par exemple, en s'appuyant sur son expérience et sa connaissance du parc, l'utilisateur (typiquement un responsable de maintenance) pourra créer une corrélation d'évènements avancée du type : "Si, sur ma fraiseuse verticale, la vitesse de rotation de la broche dépasse 60.000 tours/min et que la température de l'outil atteint 85°C dans un intervalle de 30 secondes, pendant le programme TUENC5, alors déclencher une alarme !". En complément, d'autres règles de gestion dans la plateforme peuvent implémenter un scénario d'escalade (envoi de mail par ex) ou un workflow de traitement (déclencher une opération : arrêt machine...).
Une vision globale du parc
La première étape consiste à sélectionner l'ensemble des évènements qui seront utilisés pour créer la corrélation :
- Pour les machines en fonctionnement, peuvent être utilisés :
- Les évènements remontés en temps réel liés par exemple à une supervision conditionnelle : vitesses de rotation, température de fonctionnement, vibrations, qualité de l’huile...
- Les évènements liés à leurs opérations : temps de fonctionnement, programme courant, paramétrage...
- Les informations contextuelles liées à la maintenance : date de dernière révision, indicateurs (MTBF, MTTR...)
- Les évènements issus d'autres appareils connectés à la plateforme (autres que les machines) : détecteurs de fumée, capteurs d'hygrométrie ambiante, de luminosité...
- Des évènements spécifiques issus d'autres systèmes applicatifs du Système d’Information, par exemple venant d'un broker MQTT ou service web REST
L'expertise en jeu
Vient maintenant le temps de l'élaboration de la corrélation de tous ces évènements : fort de son expertise et de sa maitrise du parc de machines, l'utilisateur va pouvoir reconstituer les conditions précises liées au dysfonctionnement à observer. Quelle que soit la complexité des évènements ou leur source, cette interface claire permet de configurer la succession de contrôles à effectuer pour créer une information à forte valeur ajoutée pour la production.
Ainsi, tout le savoir du personnel de maintenance est mis en jeu pour devenir la pierre angulaire de ce système d'analyse, pour conduire à un ensemble performant et pertinent.
Maintenance conditionnelle intelligente
Dans certains contextes, la maintenance conditionnelle devient une source de bruit important, par le grand nombre de signaux et d'alarmes qui peuvent être émis (capteurs, actionneurs, voyants...) : il peut alors être difficile de donner un sens juste à tous ces signaux même avec une parfaite connaissance du parc et des processus. Ce qui peut parfois mener à des situations à risque lorsque la multitude d'informations est finalement ignorée car trop complexe à analyser dans les délais impartis.
C'est la raison pour laquelle il est important de pouvoir regrouper tous ces signaux et les analyser simultanément afin de lever des alertes plus pertinentes et plus efficaces, et ainsi réduire les risques de faux positifs ou d'éléments perturbateurs.
En résumé, Brilliant Things apporte une solution à la complexité montante de la maintenance conditionnelle, en la rendant accessible aux personnes qui en sont les premiers acteurs.
Fichier(s) joint(s) :
IoT et interface naturelle (2) : réalité augmentée
Dans mon précédent article, j'ai présenté un type d'interface naturelle basée sur l'assistant vocal de Google.
Cette fois, nous allons explorer les possibilités de la réalité augmentée! Avant de commencer, une petite démonstration :
Comme vous pouvez le constater, le but est de récupérer en temps réel les données émises par un objet connecté (la poubelle), pour les afficher en réalité augmentée lors de son survol, avec n'importe quel type de terminal (smartphone, tablette...).
AR.js
L'interface en réalité augmentée est réalisée à partir du framework AR.js. Son intérêt principal est de s'appuyer sur les technos web (HTML, Javascript) et donc d'être utilisable sur toutes les plateformes. Voici un exemple de page web utilisée dans la démonstration précédente :
&html*
&head*
&title*ARjs&/title*
&script src="https://aframe.io/releases/0.8.0/aframe.min.js"*&/script*
&script src="https://rawgit.com/donmccurdy/aframe-extras/master/dist/aframe-extras.loaders.min.js"*&/script*
&script src="https://jeromeetienne.github.io/AR.js/aframe/build/aframe-ar.js"*&/script*
&script*
/*AFRAME.registerComponent('cursor-listener', {
init: function () {
this.el.addEventListener('click', function (evt) {
// alert('click');
});
}
});
// https://stackoverflow.com/questions/47032056/gltf-cursor-listener-click-event-in-a-frame
AFRAME.registerComponent('raycaster-autorefresh', {
init: function () {
var el = this.el;
this.el.addEventListener('model-loaded', function () {
var cursorEl = el.querySelector('[raycaster]');
cursorEl.components.raycaster.refreshObjects();
});
}
});*/
&/script*
&/head*
&body style='margin : 0px; overflow: hidden;'*
&a-scene arjs="debugUIEnabled: false;"*
&a-assets*
&img id="full" src="img/full.png"/*
&img id="empty" src="img/empty.png"/*
&img id="medium" src="img/medium.png"/*
&img id="low" src="img/low.png"/*
&img id="recycle" src="img/recycle.png"/*
&/a-assets*
&!-- handle marker with hiro preset --*
&!--&a-marker preset="hiro"*--*
&a-marker-camera preset="custom" type='pattern' url='data/can-marker.patt'*
&!--&a-image id="levelIndicator" src="#empty" width="3" height="2" position="-2 2 0"*&/a-image*--*
&a-text value="Fill level" position="0 2.6 0" width="10" anchor="left" color="#00BFFF"*&/a-text*
&a-text test-counter font="exo2bold" id="fillLevel" value="0 %" position="0 2.1 0" width="15" anchor="left" color="#00BFFF"*&/a-text*
&a-image src="#recycle" width="1" height="1" position="-1.5 -2 0"*&/a-image*
&a-text value="Next interv." position="-0.9 -1.6 0" width="10" anchor="left" color="#00BFFF"*&/a-text*
&a-text id="date" font="exo2bold" value="01 Jan 2018" position="-0.9 -2.4 0" width="20" anchor="left" color="#00BFFF"*&/a-text*
&a-entity position="-1.5 2 0"*
&a-entity id="threeDcan" gltf-model="url(obj/trash.gltf);" position="0 0 0" cursor-listener*&/a-entity*
&a-animation attribute="rotation"
dur="4000"
fill="forwards"
to="0 360 0"
repeat="indefinite"*&/a-animation*
&a-animation begin="click" attribute="position" to="-1 2 0"
easing="linear" dur="50" fill="backwards" repeat="10"*&/a-animation*
&/a-entity*
&/a-marker-camera*
&!--&a-camera*
&a-cursor*&/a-cursor*
&/a-camera*--*
&a-entity light="type: ambient; color: #FFF; intensity: 5;"*&/a-entity*
&/a-scene*
&script*
...
document.querySelector('a-scene').querySelector('#date').setAttribute('value', getDate(daysCount));
...
&/script*
&/body*
&/html*
PS : pour des raisons de compatibilité, j'ai du remplacer dans cet extrait les '<' par '&' et '>' par '*'.
En détails
Quelques précisions sur le fonctionnement de cette démonstration :
- Utilisation d'un tag personnalisé : le tag basique Hiro (utilisé par défaut par AR.js pour reconnaître la scène) a été remplacé par un tag personnalisé, via la balise :
a-marker-camera preset="custom" type='pattern' url='data/can-marker.patt'et l'outil en ligne Marker Training - Intégration de modèle 3D animé : pour permettre l'intégration dans la scène d'un modèle 3D animé, il est nécessaire de se procurer un modèle (au format GLTF, via Sketchfab par ex) et d'intégrer le script
aframe-extras.loaders.min.js - Gestion de l'éclairage : dès lors que vous ajoutez des modèles dans une scène, il faut commencer à jouer avec les lumières. Ici une lumière d'ambiance général a été mise en place :
a-entity light="type: ambient; color: #FFF; intensity: 5;" - Manipulation du DOM : c'est ce qui constitue un des points fort du framework AR.js. Vous pouvez utiliser Javascript pour manipuler les éléments de la scène 3D de la même manière que le DOM 2D standard HTML!
Connectivité
Qui dit technos web, dit Javascript : il est donc possible d'utiliser n'importe quel code (par ex websockets) pour vous connecter à la source de données (la plateforme IoT) afin de récupérer toutes les données et créer des interactions avec la réalité augmentée!
Enjoy!
Fichier(s) joint(s) :
IoT et interface naturelle
Créer des applications technologiques c'est bien, mais les rendre accessibles de manière naturelle aux utilisateurs, c'est mieux!
Voici donc un exemple de comment mettre en place un cas d'usage complet : un chatbot ("robot conversationnel" !) vocal capable de récupérer les données émises par des objets.
Scénario fonctionnel
- Un utilisateur pose une question à l'assistant vocal Google
- Une fois interprétée, la version textuelle est transmise à un chatbot
- Le chatbot comprend l'action à déclencher à partir de mots-clés
- Une requête est envoyée vers la plateforme IoT pour récupérer les données
- Le chatbot répond à l'utilisateur de manière naturelle en incluant une visualisation pertinente des données
Mise en place
Concrètement, ce genre de solution peut être mis en place à l'aide d'outils simples enchainés les uns aux autres. Pour la partie vocale, l'assistant de Google pour smartphone fait très bien le travail de reconnaissance. Le chatbot ainsi que son intelligence artificielle apportés par le service Recast.ai permettent d'apporter une fluidité très appréciée dans l'interaction homme-machine. Son atout principal repose sur le fait qu'il permet d'étendre un robot pré-entrainé, par exemple à faire la conversation, pour lui ajouter des compétences spécifiques à notre besoin. Cela garanti que même lorsque votre robot ne sera pas en mesure de reconnaitre une action correspondant à votre scénario, il répondra de manière naturelle en mode conversation.
L'autre grand intérêt de la solution Recast.ai est qu'elle est très orientée vers les développeurs, et fourni donc plusieurs types d'interfaces pour activer son robot selon les besoins : via des "BotConnector" vers les applications de messageries les plus répandues (Facebook, Slack, Tweeter, Skype...) mais également via une interface REST. Et la customisation du robot passe par l'écriture de code Javascript/NodeJS, hébergé par Recast.ai eux-mêmes.
Pour démarrer rapidement la mise en place de votre bot, vous pouvez suivre cette documentation, expliquant de A à Z toutes les étapes.
Retour d'expérience
Pour vous faciliter la tâche, voici quelques astuces afin d'optimiser l'élaboration de votre bot. Cet exemple s'appui sur les extraits de code issus de la documentation citée précédemment. Le code du bot ressemble donc à cela :
const recastai = require('recastai').default;
const client = new recastai(process.env.REQUEST_TOKEN);
const request = require('request');
const axios = require('axios');
export const bot = (body, response, callback) => {
console.log(body);
// response, the response object from the server in case of a local run
// callback, the object called in case of a bot hosting run
if (body.message) {
// pour gérer les appels par BotConnector (Slack...)
client.connect.handleMessage({body}, response, replyMessage);
} else if (body.text) {
// pour gérer les appels par API REST en direct
replyMessage(null, body.text, callback);
} else {
callback('Requete vide?!');
}
};
function replyMessage(message, textMessage, callback) {
if (message) {
console.log("handling BotConnector message");
} else {
console.log("handling API message");
}
const recastaiReq = new recastai.request(process.env.REQUEST_TOKEN, process.env.LANGUAGE);
const contentMessage = message ? message.content : textMessage;
recastaiReq.analyseText(contentMessage)
.then(recastaiRes => {
var varcontent = "";
// get the intent detected
var intent = recastaiRes.intent();
if (intent) {
console.log("intent:" + intent.slug + "/" + intent.confidence);
if (intent.slug === 'c8y_geoloc' && intent.confidence > 0.7) {
if (recastaiRes.get('asset-type') && recastaiRes.get('number')) {
// type d'objet recherché (par ex 'caisse')
var asset = recastaiRes.get('asset-type').raw;
// id de l'objet
var number = recastaiRes.get('number').raw;
axios.get('',
{
headers: {"Authorization": "Basic ..."}
})
.then(response => {
var body = response.data;
if (body.managedObject) {
//... do stuff
return message ? message.reply([{type: 'text', content: varcontent}]).then() :
callback(null, {result: varcontent, intent: intent.slug, data: dataResp});
} else {
varcontent = 'Je n\'ai rien trouvé!';
return message ? message.reply([{type: 'text', content: varcontent}]).then() :
callback(null, {result: varcontent, intent: intent.slug});
}
})
.catch(error => {
varcontent = 'Il y a eu un problème...';
return message ? message.reply([{type: 'text', content: varcontent + error}]) :
callback(error, null);
});
} else {
varcontent = 'Je ne sais pas quoi chercher...';
return message ? message.reply([{type: 'text', content: varcontent}]).then() :
callback(null, {result: varcontent, intent: intent.slug});
}
} else {
// on fait appel au moteur de conversation, pour conserver l'intelligence par defaut du bot
const converseReq = new recastai.request(process.env.REQUEST_TOKEN, process.env.LANGUAGE);
return converseReq.converseText(contentMessage)
.then(function (res2) {
// ...extract the reply...
varcontent = res2.reply();
return message ? message.reply([{type: 'text', content: varcontent}]).then() :
callback(null, {result: varcontent, intent: 'null'});
})
.catch(err => {
console.error('Something went wrong', err);
return message ? message.reply([{type: 'text', content: 'Something went wrong' + err}]) :
callback(err, null);
});
}
} else {
return message ? message.reply([{type: 'text', content: varcontent}]) :
callback(null, {result: varcontent, intent: 'null'});
}
})
.catch(err => {
console.error('Something went wrong', err);
return message ? message.reply([{type: 'text', content: 'Something went wrong' + err}]) :
callback(err, null);
});
}
Gestion des exceptions
Il est primordial que toutes les éventuelles exceptions soient correctement gérées afin que dans tous les cas, une réponse soit renvoyée, même s'il s'agit d'un message d'erreur (plus ou moins "naturel" ;), sans quoi la requête HTTP qui a appelée votre bot restera bloquée en attente d'une réponse...
Exploiter au mieux l'API Message
La plateforme Recast.ai met à disposition une API très utile permettant de créer automatiquement une réponse à un message issu d'une conversation via une messagerie instantanée (Tweeter, Slack...) tout en préservant les notions de conversation, réponse à un utilisateur spécifique, contexte du message etc, tout ceci de manière transparente via l'utilisation de la méthode message.reply(). Ainsi, quelle que soit l'origine du message ou sa forme, votre contenu sera toujours correctement interprété par la plateforme cible.
Vous avez maintenant tout ce qu'il faut pour mettre en place une interface naturelle dans votre projet!
Fichier(s) joint(s) :
Connecter une machine industrielle à Cumulocity
Il est clair aujourd'hui que c'est l'Industrie qui va drainer le plus de marché (et d'innovation) dans le domaine de l'internet des objets. Pour s'inscrire dans cette mouvance, nous allons voir comment il est possible de connecter une machine industrielle à la plateforme IoT Cumulocity.
Le protocole OPC/UA
Il s'agit d'une extension du protocole OPC, standard de l'industrie permettant la communication entre machines, qui vise à simplifier leur intégration et leur utilisation en apportant notamment de la flexibilité, de la sécurité et de l'indépendance vis-à-vis des fabricants.
Rapide point d'architecture : avec OPC/UA, chaque machine est reliée à un serveur (souvent 1 machine = 1 serveur, installé en local). Ensuite, une passerelle permet d'interroger les données des serveurs ou envoyer des opérations :
Serveurs de démo
Pour tester les fonctionnalités d'OPC/UA, il est possible d'utiliser un serveur public, comme par ex ceux d'OPCLabs, ou d'installer son propre serveur local, comme ceux fournis par Unified Automation.
Client
Le client le plus pratique à utiliser pour des premiers tests est UaExpert d'OPCLabs téléchargeable ici. C'est lui que nous utiliserons pour notre démonstration.
Premier cas d'usage
Description
Pour les besoins de cet article, nous allons nous appuyer sur les données de démo contenues dans le serveur présenté plus haut, représentant une chaudière (boiler), simulée donc par le serveur. L'idée principale est qu'une nouvelle donnée et un nouvel évènement soient générés côté Cumulocity chaque fois que la température de notre chaudière change. Voici l'architecture mise en place pour créer le lien jusqu'à Cumulocity :
Le lien vers la plateforme est réalisé par un Agent (fourni par Cumulocity) déployé sur la passerelle. Pour le récupérer et le configurer, il suffit de suivre la documentation ici.
Configuration des Devices
Voici à quoi ressemble la configuration dans Cumulocity du Device correspondant à notre passerelle :
Comme vous pouvez le voir, il est lié à un autre Device, représentant le capteur de température. Voici sa configuration :
Comme vous le constatez, nous avons configuré ce Device pour émettre un Measurement et un Event à chaque changement de valeur. Nous aurions pu également lui dire de lever une Alarme. On retrouve également à chaque fois le "Browse Path" qui permet de naviguer (à la mode OPC/UA) jusqu'à la valeur à surveiller. Cette information peut se trouver grâce au client UaExpert, dans la vue Attributes, sous le nom de BrowseName :
Testons!
Maintenant que tout est configuré, il ne nous reste plus qu'à tester! Pour cela, il faut tout d'abord démarrer l'Agent Cumulocity (voir doc). Si tout a bien été préparé, il n'y aura pas d'erreur dans la console ni d'Alarme levée sur la plateforme.
Nous pouvons maintenant, à l'aide d'UaExpert, simuler un changement de température dans notre chaudière via l'appel à la méthode dédiée "Heat" :
Une fois la méthode invoquée, nous verrons apparaitre dans Cumulocity nos valeurs de température ainsi que nos évènements :
Hope this helps!
Fichier(s) joint(s) :
Interpréter des messages Sigfox avec Cumulocity
Sigfox propose, depuis son "backend", de configurer une adresse de callback où seront transférés tous les messages émis par les objets :
De son côté, Cumulocity dispose nativement d'une brique technique (appelée Agent), capable de recevoir un message issu du réseau Sigfox et de l'interpréter, pour par exemple créer automatiquement un Device s'il n'existe pas dans l'Inventaire, lui rattacher de nouveaux messages, ou transformer les erreurs (au sens Sigfox, via la configuration de callback d'erreur) en Alarmes. Il est également possible d'envoyer des commandes vers l'objet via l'interface de Cumulocity.
Leur documentation pour la configuration de l'Agent étant très claire et précise, je vous laisse la parcourir de vous-mêmes : Configuring SIGFOX devices for Cumulocity
Une fois tout cela mis en place, les messages reçus du réseau Sigfox apparaissent en tant qu'Evènements rattachés au Device dans Cumulocity, dont le contenu correspond à la structure JSON configurée dans le callback Sigfox :
Comme vous pouvez le constater, l'évènement contient une propriété "data" qui porte les données brutes présentes dans le message Sigfox. Il faudrait maintenant pouvoir interpréter à la volée cette information pour récupérer les valeurs que nous avons stockées dans notre message, puis créer automatiquement des vrais Relevés de données (Measurements). Ceci dans le but, par exemple, de faciliter l'intégration avec des applications clientes qui seraient à l'écoute de données en temps réel issues de capteurs ou permettre l'exécution des moteurs de règles de Cumulocity pour déclencher des alarmes en fonction des valeurs reçues.
Pour ce faire, nous allons utiliser le moteur de traitement d'évènements complexes fourni par Cumulocity (Complex Event Processing).
Ainsi, dès la réception d'un message, nous allons parcourir le contenu de "data" et extraire les informations issues de nos capteurs :
Quelques explications :
Avec le langage fourni par Cumulocity (Complex Event Language), nous sélectionnons tous les évènements en temps réel issus de l'Agent Sigfox :
select * from EventCreated event where getObject(event, "com_sigfox_SigFoxData") is not null;
Puis pour chacun nous créons notre propre structure de données que nous insérons en tant que Relevé (Measurement) :
insert into CreateMeasurement select ...
Le concept de Fragment dans Cumulocity nous permet de recréer une structure JSON composée de plusieurs valeurs. Vous pouvez voir le résultat à la réception d'un message à droite de la capture d'écran, ainsi que dans le dashboard du Device :
Fichier(s) joint(s) :
Arduino et infrarouge
Aujourd'hui, un article rapide sous forme de mémo, pour mettre en place une communication série entre deux Arduinos en infrarouge. Ceci pour permettre de trouver rapidement le matériel et code nécessaires.
Avant tout, il faut savoir que dès lors qu'une communication série filaire (USB par ex) a été mise en place, il suffit de "remplacer" le fil par une liaison optique infrarouge, puisque l'encodage reste le même (UART pour de la communication série).
Code et montage
Puisqu'il est inutile de réinventer la roue, voici un très bon tutoriel de Zola lab expliquant quel montage et quel code utiliser pour mettre en place la liaison optique :
Matériel
Et pour se fournir le bon matériel pour réaliser ce montage, rendez-vous chez Lextronic :
Have fun!
Fichier(s) joint(s) :
Tester une API REST avec Swagger, Postman et Jenkins
Avec l'essor de l'IoT vient l'âge d'or des APIs (et de REST), pour ouvrir facilement et de manière universelle des services.
La gestion des APIs devient donc une affaire de pros et doit être de plus en plus rigoureuse. Nous allons voir comment maîtriser les évolutions d'une API à partir d'outils spécialisés.
Les basiques
Je pense qu'il n'est plus nécessaire de présenter Postman (extension Chrome pour tester une API) ni Jenkins (serveur d'intégration continue). Mais il est intéressant de voir comment les deux peuvent travailler ensemble pour devenir une suite de tests automatisée d'API, via l'utilitaire en ligne de commande Newman et cette très bonne documentation officielle.
Swagger, la documentation simple et efficace!
Le meilleur moyen de s'assurer de la cohérence d'une API reste une bonne documentation. Pour ce faire, il existe Swagger (et sa version en ligne SwaggerHub) qui est l'outil de référence en la matière. En effet, il est très simple d'écrire rapidement une documentation pertinente, avec qui plus est une présentation agréable :
Putting it all together
Maintenant, voyons comment combiner tout cela.
Avec Swagger, vous avez la possibilité de définir, en plus de vos URLs, le formalisme des objets métier que vous manipulez. Pour cela il faut utiliser la section "definitions". Par exemple :
paths:
...
definitions:
Asset :
type: object
additionalProperties: false
required: ['name','owner','type']
properties :
name:
type: string
description: Nom de l'Asset.
owner:
type: string
description: Propriétaire du Device (User / domaine métier)
type:
type: string
description: Type de l'objet
Zone :
type: object
additionalProperties: false
required: ['name','geojson']
properties :
name:
type: string
description: Nom de la Zone.
geojson :
type: object
description: Géométrie au format GeoJSON
properties:
type:
type: string
description: Feature
geometry:
type: object
properties:
type:
type: string
description: Point
coordinates:
type: array
description: tableau de points GPS
items:
type: number
country:
type: string
description: Pays de la Zone, issu du référentiel Country.
Parallèlement, il est possible dans les tests Postman de vérifier que le contenu d'une réponse correspond à un schéma particulier (via TinyValidator). Et surprise, le formalisme des "schémas" utilisés par Postman correspond parfaitement à celui de nos "definitions" Swagger! Cela vous donne des idées?? Alors allons-y!
Tout d'abord, nous allons récupérer notre définition Swagger en l'exportant au format JSON pour pouvoir l'utiliser avec Postman. Cependant, à l'heure actuelle, il n'est pas possible dans Postman de charger un fichier externe, autre qu'un fichier de données de tests. Donc nous allons devoir faire un copié/collé de notre définition dans Postman. Mais pour essayer de faire cela proprement, nous allons coller notre Swagger dans une variable d'environnement de Postman, afin qu'elle soit accessible dans tous les tests :
Ensuite, nous allons écrire un test de vérification de schéma qui va se baser sur la définition d'un de nos objets métier Swagger :
Comme vous le voyez, nous chargeons en JSON notre définition Swagger, puis définissons un schéma de type tableau d'Assets, en récupérant la définition des Assets avec swaggerJson.definitions.Asset. Et en plus c'est facile!
Maintenant, lors de l'exécution de votre requête, les tests seront lancés et la réponse sera validée en fonction du schéma indiqué! Il est important à ce stade de disposer d'une définition d'objet stricte, notamment via l'utilisation des propriétés Swagger additionalProperties:false et required: ['name','owner','type'], sans quoi n'importe quelle réponse passera la validation.
Vous pouvez même aller jusqu'à utiliser votre définition Swagger pour récupérer les URLs à tester. Pour cela, il faut utiliser la partie "Pre-request Script" de Postman et parcourir de la même façon notre Swagger :
Ici, nous définissons une variable d'environnement 'url' à l'aide des attributs 'host' et 'basePath' de Swagger. Cette variable est ensuite utilisée par Postman via la syntaxe {{url}} dans sa barre d'adresse.
Remarque :
Il est également possible d'importer directement la définition Swagger dans Postman (via le bouton Import) pour récupérer automatiquement toutes les URLs à tester. Cependant, si vous avez besoin de réimporter votre définition suite à des modifications, cela va écraser tout ce qui existe déjà, y compris les tests!.
Pour terminer, il ne vous reste plus qu'à mettre tout cela dans Jenkins, via l'export de votre collection et de l'environnement Postman que vous passerez en paramètres de Newman! Enjoy!
Fichier(s) joint(s) :
Cumulocity : une plateforme IoT taillée pour l'industrie
Désormais c'est un fait, c'est bien l'industrie qui va guider les prochaines évolutions majeures de l'IoT, pousser l'adoption de certaines normes et standardiser les principaux cas d'usages.
Il est donc temps de commencer à prendre en main les plateformes spécialisées, délivrant des services professionnels : device management, intégration de protocoles, sécurité, mise à l'échelle, analytics, multitenants...
Pour cela, nous allons utiliser la plateforme Cumulocity.
Son nom ne vous dit peut-être rien, mais elle fait partie des leaders du marché, avec déjà de belles références. Pour en avoir comparé bon nombre, je la place grande première de mon top 3 des plateformes, juste devant IBM Watson IoT et theThings.io (je n'oublie bien sûr pas Thingworx de PTC, mais elle est à mon sens beaucoup trop complexe à mettre en oeuvre, et reste difficile d'accès aux néophytes du PLM...).
Prise en main en 5 minutes
Pour la tester, rien de plus simple, il suffit de créer un compte d'essai gratuit, qui donne accès à la majeure partie des fonctionnalités pendant 1 mois. Ensuite, il est possible d'ajouter par exemple un smartphone en tant que Device, via l'appli Android spécifique. Vous aurez alors accès à un dashboard affichant en temps réel les données relevées par votre téléphone (gyroscope, accéléromètre, luxmètre...).
Explorer toute l'API REST
Puisqu'une plateforme IoT n'est rien sans une interface REST, il est assez simple d'explorer toute l'étendue des possibilités offertes par celle de Cumulocity. Pour cela, le plus pratique est d'utiliser l'extension Chrome Postman et récupérer la librairie publiée par Cumulocity. Vous aurez ainsi sous la main toutes les requêtes prêtes à être utilisées pour envoyer des données, créer des Devices, déclencher des alarmes, pousser des Opérations...
Premier cas concret
Maintenant que nous avons fait les premiers tests de la plateforme, il est temps de créer un vrai cas d'usage du monde réel. Voici le scénario envisagé :
Des Devices émettent des données vers la plateforme (quel que soit le protocole) qui sont reçues en temps réel sur des serveurs ou des applications d'analyse. Très commun! Cependant, selon les services offerts par les API REST, il n'est pas toujours trivial de mettre en place un mécanisme de réception en temps réel de la dernière donnée émise ou alarme levée par un Device spécifique.
Cumulocity offre de ce point de vue 2 outils intéressants :
- des connexions par websockets, qui permettent de créer des flux de données en temps réel, plus robustes pour ce besoin que du simple HTTP (même en long-polling)
- un ensemble de flux d'évènements internes au framework, sur lesquels il est possible de s'abonner, pour recevoir des notifications sur TOUT ce qui se passe sur la plateforme (réception de données, création de Device, déclenchement d'évènements/alarmes...)
Pour établir une connexion, il suffit de respecter le protocole de Bayeux, basé les concepts de publish/subsribe et d'échanges de données en JSON. La documentation de Cumulocity à ce sujet permet de rapidement mettre en place ces flux temps réel.
Afin de réaliser une implémentation la plus simple possible et la plus polyvalente, utilisable tant par un serveur web qu'embarqué dans un Device, nous allons utiliser le langage Python.
Gestion des websockets
Notre premier module s'occupera de la gestion bas niveau des websockets, grâce au package websocket-client. Vous aurez également besoin de simplejson pour créer du contenu JSON.
import websocket
import simplejson
class WebsocketWrapper:
def __init__(self, cnxUrl):
self.ws = websocket.WebSocketApp(cnxUrl,
on_message = self.on_message,
on_error = self.on_error,
on_close = self.on_close)
self.ws.on_open = self.on_open
self.servermsgjson = []
def on_error(self, ws, error):
print(error)
def on_close(self, ws):
print('closed')
def on_open(self, ws):
print('connected')
self.afterOpen()
def on_message(self, ws, message):
print("server msg: "+message)
self.servermsgjson = simplejson.loads(message)
self.parseMsg(self.servermsgjson)
def send(self,msg):
self.ws.send(msg)
def openWebsocket(self):
print('opening websocket...')
# websocket.enableTrace(True)
self.ws.run_forever(http_proxy_host='...', http_proxy_port=80)
def afterOpen(self):
pass
def parseMsg(self, jsonMsg):
pass
def handshake(self):
self.ws.send(simplejson.dumps([{'channel':'/meta/handshake','ext':{'com.cumulocity.authn':{'token':'...'}},'version':'1.0','mininumVersion':'1.0beta','supportedConnectionTypes':['websocket','long-polling','callback-polling'],'advice':{'timeout':120000,'interval':30000}}]))
def connect(self, clientId):
print('realtime connection...')
self.ws.send(simplejson.dumps([{"channel":"/meta/connect","connectionType":"websocket","clientId":clientId}]))
Il vous faudra ici modifier l'adresse du proxy (ou supprimer les paramètres s'il n'y a pas de proxy) et ajouter le token d'authentification (usr:pwd encodés en Base64).
Clients websockets
Le code ci-dessous va permettre de créer 2 clients en websockets écoutant des flux différents : gestion des évènements et réception de mesures.
import simplejson
from websocket.websocket_wrapper import WebsocketWrapper
class C8YClient:
def __init__(self):
self.clientId = ''
self.findClientId = False
self.readyToConnect = False
self.wrapper = WebsocketWrapper('wss://.cumulocity.com/cep/realtime')
self.wrapper.parseMsg = self.parseMsg
self.wrapper.afterOpen = self.afterOpen
def init(self):
self.wrapper.openWebsocket()
def afterOpen(self):
self.initClient()
def initClient(self):
self.findClientId = True
self.wrapper.handshake()
def parseMsg(self, jsonMsg):
if self.findClientId:
self.findClientId = False
self.doFindClientId(jsonMsg)
self.doSubscription()
elif self.readyToConnect:
self.readyToConnect = False
self.connectClient()
else:
self.useMsg(jsonMsg)
def useMsg(self, jsonMsg):
pass
def doSubscription(self):
pass
def doFindClientId(self, jsonMsg):
self.clientId = jsonMsg[0]['clientId']
print('got clientId : ' + self.clientId)
def connectClient(self):
self.wrapper.connect(self.clientId)
def subscribe(self, channel):
print('subscribing to '+channel+'...')
self.readyToConnect = True
self.wrapper.send(simplejson.dumps([{"channel":"/meta/subscribe","subscription":"/"+channel+"/*","clientId":self.clientId}]))
class MeasurementsClient(C8YClient):
def __init__(self):
super().__init__()
def initClient(self):
print('initMeasurementsClient...')
super().initClient()
def doSubscription(self):
super().subscribe('measurements')
def useMsg(self, jsonMsg):
print('new measurement : ' + simplejson.dumps(jsonMsg))
class EventsClient(C8YClient):
def __init__(self):
super().__init__()
def initClient(self):
print('initEventsClient...')
super().initClient()
def doSubscription(self):
super().subscribe('events')
def useMsg(self, jsonMsg):
print('new event : ' + simplejson.dumps(jsonMsg))
Ne reste plus qu'un petit main pour exécuter tout cela :
from threading import Thread
from websocket.c8y_client import MeasurementsClient, EventsClient
if __name__ == '__main__':
measurementsClient = MeasurementsClient()
threadM = Thread(target = measurementsClient.init, args = ())
threadM.start()
eventsClient = EventsClient()
threadE = Thread(target = eventsClient.init, args = ())
threadE.start()
Nous disposons maintenant d'une simple application Python qui reçoit en temps réel des notifications d'évènements de Cumulocity! Vous pouvez le tester simplement via les requêtes REST déjà existantes dans la librairie Postman. Ainsi, POSTer un nouveau Measurement (relevé de données issu d'un capteur) déclenchera immédiatement sa réception côté Python.
A vous l'IIoT !





























