Aller au contenu principal

API

API​

optiCLOUD met à disposition plusieurs interfaces techniques pour raccorder des appareils, lire des données et automatiser des processus. Au quotidien, deux types d'accès sont surtout pertinents :

  • l'API REST pour l'authentification, la gestion des appareils, l'accès aux fichiers et les requêtes via HTTP
  • l'API MQTT pour l'échange de données directement entre l'appareil et la plateforme

Cette page résume les flux de base de l'API et sert de point d'entrée technique pour les intégrations et les scripts.

Mise en perspective​

Les deux API remplissent des fonctions différentes :

  • L'API REST convient aux scripts backend, aux analyses, aux téléchargements de fichiers, à la recherche d'appareils et aux opérations de gestion.
  • L'API MQTT convient aux appareils, aux gateways et aux systèmes embarqués qui doivent envoyer ou récupérer directement de la télémétrie ou des attributs auprès de la plateforme.

La API Description est en outre accessible via le User Menu. Les points d'accès REST disponibles y sont documentés de façon interactive.

Notions de base de l'API REST​

Authentification​

Pour les accès REST, une session est d'abord établie. Le déroulement typique comprend deux étapes :

  1. POST /api/noauth/authorization
  2. POST /api/auth/login

À la première étape, le nom d'utilisateur et le mot de passe sont envoyés à la plateforme. La réponse est une liste des tenants auxquels l'utilisateur a accès. Ensuite, l'authorityId est extrait de l'association de tenant souhaitée et envoyé au point d'accès de connexion avec le nom d'utilisateur et le mot de passe.

En retour, la plateforme fournit un token, à joindre aux requêtes suivantes dans l'en-tête X-Authorization: Bearer <token>.

Un exemple possible en Python :

import requests

BASEURL = "https://example.opticloud.io"
CREDENTIALS = {"username": "user@example.com", "password": "secret"}
TENANT = "MyTenant"

def login(baseurl, credentials, tenant):
auth_response = requests.post(
baseurl + "/api/noauth/authorization",
json=credentials,
verify=True,
)
auth_response.raise_for_status()

selected = None
for entry in auth_response.json():
try:
if entry["tenant"]["name"] == tenant:
selected = entry
break
except KeyError:
continue

if selected is None:
raise RuntimeError("Tenant not found")

login_body = {
"username": credentials["username"],
"password": credentials["password"],
"authorityId": selected["authorityId"]["id"],
}

login_response = requests.post(
baseurl + "/api/auth/login",
json=login_body,
verify=True,
)
login_response.raise_for_status()
token = login_response.json()["token"]
return {
"X-Authorization": f"Bearer {token}",
"Accept": "text/html, application/json, application/xhtml+xml, application/xml;q=0.9",
}
remarque

La sélection concrète du tenant n'est nécessaire que si un utilisateur a accès à plusieurs tenants.

Récupérer la liste des appareils d'un tenant​

Pour lister les appareils d'un tenant, le point d'accès GET /api/tenants/{tenant_id}/devices peut être utilisé. On définit généralement pour cela les paramètres page=0 et limit=-1 afin de récupérer tous les appareils en une seule réponse.

def get_devices(baseurl, header, tenant_id):
response = requests.get(
baseurl + f"/api/tenants/{tenant_id}/devices",
headers=header,
params={"page": 0, "limit": -1},
verify=True,
)
response.raise_for_status()
return response.json()["data"]

On obtient ainsi notamment les ID d'appareils, nécessaires pour d'autres appels d'API.

Interroger les fichiers OSF d'un appareil​

Les fichiers OSF traités ou déjà connus du serveur peuvent être récupérés via GET /api/devices/{device_id}/data-files/original.

Paramètres typiques :

  • startTime comme horodatage Unix en millisecondes
  • endTime comme horodatage Unix en millisecondes
  • limit
  • page
def get_processed_osf_files(baseurl, header, device_id, start_time, end_time):
response = requests.get(
baseurl + f"/api/devices/{device_id}/data-files/original",
headers=header,
params={
"startTime": start_time,
"endTime": end_time,
"limit": -1,
"page": 0,
},
verify=True,
)
response.raise_for_status()
return response.json()["data"]

Interroger la liste complète des fichiers et l'état de stockage​

Si l'on a besoin non seulement des fichiers disponibles côté serveur, mais de tous les fichiers connus avec leur statut, le point d'accès /API/filelist/list/... peut être utilisé en complément.

Quelques particularités sont à noter :

  • Le point d'accès fournit du XML au lieu de JSON.
  • L'URL utilise /API/ en majuscules.
  • Les indications de temps sont ici attendues en secondes, et non en millisecondes.

Le token de l'appareil est en outre d'abord nécessaire. Il peut être lu via GET /api/device/{deviceId}/credentials. Le champ pertinent y est credentialsId.

import xmltodict

def get_listed_files(baseurl, header, device_id, start_time, end_time):
credentials_response = requests.get(
baseurl + f"/api/device/{device_id}/credentials",
headers=header,
verify=True,
)
credentials_response.raise_for_status()
credentials_id = credentials_response.json()["credentialsId"]

list_response = requests.get(
baseurl + f"/API/filelist/list/deviceid/{credentials_id}/StartTime/{start_time}/{end_time}",
headers=header,
verify=True,
)
list_response.raise_for_status()

list_dict = xmltodict.parse(list_response.content)
return list_dict["FileList"]["GeoLogFile"]

Valeurs de statut possibles :

  • 0 présent uniquement sur l'appareil
  • 1 demandé par le serveur
  • 2 présent uniquement sur le serveur
  • 3 présent à la fois sur l'appareil et sur le serveur
info

Pour ce point d'accès XML, un en-tête Accept prenant en charge XML doit être défini. Sans en-tête approprié, la plateforme peut répondre par 406 Not Acceptable.

remarque

Pour lire les informations d'identification d'un appareil, le compte utilisateur a besoin des droits appropriés, en particulier pour l'affichage des appareils et des informations d'identification des appareils.

Télécharger des fichiers OSF​

Le téléchargement de fichiers se fait généralement en deux étapes :

  1. POST /api/device/{device_id}/data-files/CLIENT_SCOPE/download
  2. GET /api/device/{device_id}/data-files/CLIENT_SCOPE/token/{download_token}

À la première étape, le fichier souhaité est demandé par son nom de fichier. La réponse contient un token de téléchargement. À la seconde étape, le fichier proprement dit est téléchargé avec ce token.

import os

def download_processed_osf_file(baseurl, header, device_id, filename, savedir):
response = requests.post(
baseurl + f"/api/device/{device_id}/data-files/CLIENT_SCOPE/download",
headers=header,
json=[filename],
verify=True,
)
response.raise_for_status()
download_token = response.text

file_response = requests.get(
baseurl + f"/api/device/{device_id}/data-files/CLIENT_SCOPE/token/{download_token}",
headers=header,
verify=True,
)
file_response.raise_for_status()

target = os.path.join(savedir, filename.split("/")[-1])
with open(target, "wb") as handle:
handle.write(file_response.content)
return target
info

Lorsque plusieurs fichiers sont demandés simultanément, la réponse est généralement une archive ZIP et non un fichier .osfz unique.

Récupérer les dernières données de télémétrie d'un appareil​

Pour récupérer les valeurs actuelles, GET /api/plugins/telemetry/DEVICE/{device_id}/values/timeseries peut être utilisé. Dans de nombreux cas, startTs et endTs sont simplement définis sur l'instant actuel lorsque seules les valeurs les plus récentes sont nécessaires.

import time

def get_latest_telemetry(baseurl, header, device_id):
now_ms = int(time.time() * 1000)
response = requests.get(
baseurl + f"/api/plugins/telemetry/DEVICE/{device_id}/values/timeseries",
headers=header,
params={"startTs": now_ms, "endTs": now_ms},
verify=True,
)
response.raise_for_status()
return response.json()

Le résultat contient les valeurs actuelles par canal de données, avec leurs horodatages.

Récupérer la communication de l'appareil​

Les événements de communication d'un appareil peuvent être interrogés via GET /api/devices/{deviceId}/communication/data. Selon l'appareil et la configuration de la plateforme, cela comprend par exemple des entrées relatives à PING, ATTN ou aux retours de fichiers.

Paramètres requis :

  • startTime
  • endTime
  • limit
  • page
def get_device_communication(baseurl, header, device_id, start_ts, end_ts):
response = requests.get(
baseurl + f"/api/devices/{device_id}/communication/data",
headers=header,
params={
"startTime": start_ts,
"endTime": end_ts,
"limit": 2147483647,
"page": 0,
},
verify=True,
)
response.raise_for_status()
return response.json()

Envoyer des commandes à un appareil​

Certains points d'accès REST servent à placer des commandes pour un appareil dans la file d'attente. Ces points d'accès n'établissent normalement aucune connexion en ligne directe avec l'appareil. La commande est plutôt traitée à la prochaine connexion de l'appareil.

Un exemple est la liste des fichiers via LIST_FILES. POST /api/devices/{deviceId}/request-list est utilisé pour cela.

Le corps de la requête contient généralement :

  • startTime au format ISO 8601
  • endTime au format ISO 8601
  • tag, souvent par exemple preview
def request_list(baseurl, header, device_id, start_time, end_time, tag):
response = requests.post(
baseurl + f"/api/devices/{device_id}/request-list",
headers=header,
json={
"startTime": start_time,
"endTime": end_time,
"tag": tag,
},
verify=True,
)
response.raise_for_status()

Notions de base de l'API MQTT​

L'interface MQTT est destinée aux appareils qui communiquent directement avec optiCLOUD. Les tâches typiques sont :

  • envoyer de la télémétrie
  • téléverser des attributs client
  • demander des attributs partagés ou client
  • s'abonner aux attributs partagés

L'authentification se fait généralement via le token d'accès de l'appareil comme nom d'utilisateur MQTT.

Format JSON pris en charge​

L'API MQTT utilise par défaut un format clé-valeur basé sur JSON. Les clés sont des chaînes ; les valeurs peuvent être par exemple des chaînes, des booléens, des nombres ou encore des objets JSON imbriqués.

{
"stringKey": "value1",
"booleanKey": true,
"doubleKey": 42.0,
"longKey": 73,
"jsonKey": {
"someNumber": 42,
"someArray": [1, 2, 3],
"someNestedObject": {
"key": "value"
}
}
}

Envoyer de la télémétrie par MQTT​

Pour téléverser des données de télémétrie, on publie sur le topic v1/devices/me/telemetry.

Forme simple sans horodatage propre :

{"temperature": 42}

Un tableau d'objets est également possible :

[{"key1": "value1"}, {"key2": true}]

Si aucun horodatage n'est envoyé, la plateforme utilise l'horodatage côté serveur.

Si l'appareil peut lui-même saisir des horodatages, le format suivant est le plus souvent utilisé :

{
"ts": 1451649600512,
"values": {
"temperature": 42,
"humidity": 55
}
}

ts est ici un horodatage Unix en millisecondes.

Un exemple avec mosquitto_pub :

mosquitto_pub -d -q 1 \
-h "demo.opticloud.io" \
-t "v1/devices/me/telemetry" \
-u "$ACCESS_TOKEN" \
-m '{"temperature":42}'
info

Remplacez demo.opticloud.io par votre hôte et $ACCESS_TOKEN par le token d'accès de l'appareil.

Envoyer des attributs par MQTT​

Les attributs d'appareil côté client sont publiés sur le topic v1/devices/me/attributes.

Exemple :

{
"attribute1": "value1",
"attribute2": true
}

Exemple avec mosquitto_pub :

mosquitto_pub -d \
-h "demo.opticloud.io" \
-t "v1/devices/me/attributes" \
-u "$ACCESS_TOKEN" \
-m '{"attribute1":"value1","attribute2":true}'

Demander des attributs au serveur​

Les appareils peuvent aussi demander activement au serveur des attributs client et partagés. Pour cela, on publie sur un topic de requête :

v1/devices/me/attributes/request/$request_id

L'appareil doit en même temps s'abonner au topic de réponse correspondant :

v1/devices/me/attributes/response/+

Comme la publication et l'abonnement doivent avoir lieu au sein de la même session MQTT, cela est souvent mis en œuvre avec une bibliothèque cliente MQTT. Un exemple minimal avec mqtt.js :

var mqtt = require('mqtt');

var client = mqtt.connect('mqtt://demo.opticloud.io', {
username: process.env.TOKEN
});

client.on('connect', function () {
client.subscribe('v1/devices/me/attributes/response/+');
client.publish(
'v1/devices/me/attributes/request/1',
'{"clientKeys":"attribute1,attribute2","sharedKeys":"shared1,shared2"}'
);
});

client.on('message', function (topic, message) {
console.log('response.topic: ' + topic);
console.log('response.body: ' + message.toString());
client.end();
});

Pour démarrer l'exemple :

export TOKEN=$ACCESS_TOKEN
node mqtt-js-attributes-request.js

Conseils pour la pratique​

Pour les intégrations en production, les points suivants sont surtout importants :

  • REST et MQTT ont des domaines de responsabilité différents et doivent être combinés de façon ciblée.
  • Certains points d'accès travaillent en millisecondes, d'autres en secondes.
  • Pour les points d'accès XML, un en-tête Accept approprié est important.
  • Les commandes envoyées aux appareils sont souvent seulement placées dans une file d'attente et non exécutées immédiatement.
  • Pour les points d'accès sensibles tels que les informations d'identification des appareils, des autorisations supplémentaires sont requises.

Mise en perspective​

L'API constitue la base technique des intégrations externes, des logiciels d'appareils, des scripts d'automatisation et des analyses spécifiques aux clients autour d'optiCLOUD.

Pour l'utilisation via l'interface, les sections Control Center, Dashboards et Automation sont particulièrement pertinentes. L'API complète ces sections lorsque des données ou fonctions doivent être traitées en dehors de l'interface standard.