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:
POST /api/noauth/authorizationPOST /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",
}
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:
startTimecome timestamp Unix in millisecondiendTimecome timestamp Unix in millisecondilimitpage
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:
0presente solo sul dispositivo1richiesto dal server2presente solo sul server3presente sia sul dispositivo sia sul server
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.
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:
POST /api/device/{device_id}/data-files/CLIENT_SCOPE/downloadGET /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
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:
startTimeendTimelimitpage
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:
startTimein formato ISO 8601endTimein formato ISO 8601tag, spesso ad esempiopreview
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}'
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
Acceptadeguato. - 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.