Passa al contenuto principale

API

API​

optiCLOUD mette a disposizione diverse interfacce tecniche per collegare dispositivi, leggere dati e automatizzare processi. Nell'uso quotidiano sono rilevanti soprattutto due tipi di accesso:

  • la REST API per autenticazione, gestione dei dispositivi, accesso ai file e interrogazioni tramite HTTP
  • la MQTT API per lo scambio di dati direttamente tra dispositivo e piattaforma

Questa pagina riassume i flussi API fondamentali e funge da punto di ingresso tecnico per integrazioni e script.

Inquadramento​

Le due API assolvono compiti diversi:

  • la REST API è adatta a script backend, analisi, download di file, ricerca di dispositivi e operazioni di gestione.
  • la MQTT API è adatta a dispositivi, gateway e sistemi embedded che devono inviare o richiamare direttamente alla piattaforma telemetria o attributi.

Inoltre, tramite lo User Menu è raggiungibile la API Description. Qui gli endpoint REST disponibili sono documentati in modo interattivo.

Fondamenti della REST API​

Autenticazione​

Per gli accessi REST si stabilisce anzitutto una sessione. La procedura tipica consiste in due passaggi:

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

Nel primo passaggio nome utente e password vengono inviati alla piattaforma. Come risposta viene restituito un elenco dei tenant ai quali l'utente ha accesso. Successivamente, dall'assegnazione al tenant desiderato si ricava l'authorityId, che viene inviato insieme a nome utente e password all'endpoint di login.

Come risultato la piattaforma restituisce un token, che nelle richieste successive viene fornito nell'header X-Authorization: Bearer <token>.

Un possibile esempio in 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",
}
note

La selezione concreta del tenant è necessaria solo se un utente ha accesso a più tenant.

Recuperare l'elenco dei dispositivi di un tenant​

Per elencare i dispositivi di un tenant è possibile utilizzare l'endpoint GET /api/tenants/{tenant_id}/devices. Tipicamente si impostano a tale scopo i parametri page=0 e limit=-1, per recuperare tutti i dispositivi in un'unica risposta.

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"]

In questo modo si ottengono, tra l'altro, gli ID dei dispositivi, necessari per ulteriori chiamate API.

Interrogare i file OSF di un dispositivo​

I file OSF elaborati o già noti al server possono essere recuperati tramite GET /api/devices/{device_id}/data-files/original.

Parametri tipici:

  • startTime come timestamp Unix in millisecondi
  • endTime come timestamp Unix in millisecondi
  • 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"]

Interrogare l'elenco completo dei file e lo stato di memorizzazione​

Se non servono solo i file disponibili lato server, ma tutti i file noti incluso lo stato, è possibile utilizzare inoltre l'endpoint /API/filelist/list/....

In questo caso occorre tenere presenti alcune particolarità:

  • L'endpoint restituisce XML invece di JSON.
  • L'URL utilizza /API/ in maiuscolo.
  • Le indicazioni di tempo qui sono attese in secondi, non in millisecondi.

Inoltre è necessario anzitutto il token del dispositivo. Questo può essere letto tramite GET /api/device/{deviceId}/credentials. Qui è rilevante il campo 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"]

Possibili valori di stato:

  • 0 presente solo sul dispositivo
  • 1 richiesto dal server
  • 2 presente solo sul server
  • 3 presente sia sul dispositivo sia sul server
informazioni

Per questo endpoint XML dovrebbe essere impostato un header Accept con supporto XML. Senza un header adeguato la piattaforma può rispondere con 406 Not Acceptable.

note

Per leggere le credenziali dei dispositivi, l'account utente necessita dei diritti adeguati, in particolare per la visualizzazione dei dispositivi e delle credenziali dei dispositivi.

Scaricare i file OSF​

Il download dei file avviene tipicamente in due passaggi:

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

Nel primo passaggio il file desiderato viene richiesto tramite il nome del file. La risposta contiene un token di download. Nel secondo passaggio il file vero e proprio viene scaricato con questo 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
informazioni

Se vengono richiesti più file contemporaneamente, la risposta è di norma un archivio ZIP e non un singolo file .osfz.

Recuperare gli ultimi dati di telemetria di un dispositivo​

Per il recupero dei valori correnti è possibile utilizzare GET /api/plugins/telemetry/DEVICE/{device_id}/values/timeseries. In molti casi startTs e endTs vengono semplicemente impostati sul momento attuale, se servono solo i valori più recenti.

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()

Il risultato contiene i valori correnti per ogni canale dati insieme ai timestamp.

Recuperare la comunicazione del dispositivo​

Gli eventi di comunicazione di un dispositivo possono essere interrogati tramite GET /api/devices/{deviceId}/communication/data. Ne fanno parte, a seconda del dispositivo e della configurazione della piattaforma, ad esempio voci relative a PING, ATTN o restituzioni di file.

Parametri necessari:

  • 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()

Inviare comandi a un dispositivo​

Alcuni endpoint REST servono a inserire in coda comandi per un dispositivo. Questi endpoint normalmente non stabiliscono una connessione online diretta con il dispositivo. Il comando viene invece elaborato alla successiva connessione del dispositivo.

Un esempio è l'elenco dei file tramite LIST_FILES. A tale scopo si utilizza POST /api/devices/{deviceId}/request-list.

Il request body contiene tipicamente:

  • startTime in formato ISO 8601
  • endTime in formato ISO 8601
  • tag, spesso ad esempio 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()

Fondamenti della MQTT API​

L'interfaccia MQTT è pensata per i dispositivi che comunicano direttamente con optiCLOUD. I compiti tipici sono:

  • inviare telemetria
  • caricare attributi client
  • richiedere attributi shared o client
  • sottoscrivere attributi shared

L'autenticazione avviene di norma tramite l'access token del dispositivo come nome utente MQTT.

Formato JSON supportato​

La MQTT API utilizza per impostazione predefinita un formato chiave-valore basato su JSON. Le chiavi sono stringhe, i valori possono essere, ad esempio, stringhe, valori booleani, numeri o anche oggetti JSON annidati.

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

Inviare telemetria tramite MQTT​

Per caricare dati di telemetria si pubblica sul topic v1/devices/me/telemetry.

Forma semplice senza timestamp proprio:

{"temperature": 42}

In alternativa è possibile anche un array di oggetti:

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

Se non viene inviato alcun timestamp, la piattaforma utilizza il timestamp lato server.

Se il dispositivo è in grado di rilevare autonomamente i timestamp, di norma si utilizza il seguente formato:

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

Qui ts è un timestamp Unix in millisecondi.

Un esempio con mosquitto_pub:

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

Sostituire demo.opticloud.io con il proprio host e $ACCESS_TOKEN con l'access token del dispositivo.

Inviare attributi tramite MQTT​

Gli attributi dei dispositivi lato client vengono pubblicati sul topic v1/devices/me/attributes.

Esempio:

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

Esempio con mosquitto_pub:

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

Richiedere attributi al server​

I dispositivi possono anche richiedere attivamente al server attributi client e shared. A tale scopo si pubblica su un topic di richiesta:

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

Contemporaneamente il dispositivo deve sottoscrivere il topic di risposta corrispondente:

v1/devices/me/attributes/response/+

Poiché publish e subscribe devono avvenire all'interno della stessa sessione MQTT, ciò viene spesso realizzato con una libreria client MQTT. Un esempio minimo con 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();
});

Per avviare l'esempio:

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

Indicazioni per la pratica​

Per le integrazioni produttive sono importanti soprattutto questi punti:

  • REST e MQTT hanno competenze diverse e dovrebbero essere combinati in modo mirato.
  • Alcuni endpoint lavorano con millisecondi, altri con secondi.
  • Per gli endpoint XML è importante un header Accept adeguato.
  • I comandi ai dispositivi vengono spesso solo inseriti in una coda e non eseguiti immediatamente.
  • Per endpoint sensibili come le credenziali dei dispositivi sono necessarie autorizzazioni aggiuntive.

Inquadramento​

L'API costituisce la base tecnica per integrazioni esterne, software dei dispositivi, script di automazione e analisi specifiche per il cliente intorno a optiCLOUD.

Per l'utilizzo tramite l'interfaccia sono rilevanti in particolare le aree Control Center, Dashboards e Automation. L'API integra queste aree quando dati o funzioni devono essere elaborati al di fuori dell'interfaccia standard.