Saltar al contenido principal

API

API​

optiCLOUD ofrece varias interfaces técnicas para conectar dispositivos, leer datos y automatizar procesos. En el día a día son relevantes sobre todo dos tipos de acceso:

  • la REST API, para autenticación, gestión de dispositivos, acceso a archivos y consultas mediante HTTP
  • la MQTT API, para el intercambio de datos directamente entre el dispositivo y la plataforma

Esta página resume los flujos básicos de la API y sirve como introducción técnica para integraciones y scripts.

Contexto​

Las dos API cumplen funciones distintas:

  • La REST API es adecuada para scripts de backend, evaluaciones, descargas de archivos, búsqueda de dispositivos y operaciones de administración.
  • La MQTT API es adecuada para dispositivos, gateways y sistemas embebidos que deban enviar o consultar telemetría o atributos directamente a la plataforma.

Además, desde el User Menu se puede acceder a la API Description. Allí se documentan de forma interactiva los endpoints REST disponibles.

Fundamentos de la REST API​

Autenticación​

Para los accesos REST se establece primero una sesión. El flujo típico consta de dos pasos:

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

En el primer paso se envían a la plataforma el nombre de usuario y la contraseña. Como respuesta se recibe una lista de los tenants a los que tiene acceso el usuario. A continuación, de la asignación de tenant deseada se toma el authorityId y se envía, junto con el nombre de usuario y la contraseña, al endpoint de inicio de sesión.

Como resultado, la plataforma devuelve un token que se incluye en las solicitudes posteriores en el encabezado X-Authorization: Bearer <token>.

Un posible ejemplo 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",
}
nota

La selección concreta del tenant solo es necesaria si un usuario tiene acceso a varios tenants.

Obtener la lista de dispositivos de un tenant​

Para listar los dispositivos de un tenant se puede utilizar el endpoint GET /api/tenants/{tenant_id}/devices. Normalmente se establecen para ello los parámetros page=0 y limit=-1, con el fin de obtener todos los dispositivos en una sola respuesta.

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

De este modo se obtienen, entre otros datos, los ID de dispositivo necesarios para otras llamadas a la API.

Consultar los archivos OSF de un dispositivo​

Los archivos OSF procesados o ya conocidos por el servidor pueden obtenerse mediante GET /api/devices/{device_id}/data-files/original.

Parámetros típicos:

  • startTime, como marca de tiempo Unix en milisegundos
  • endTime, como marca de tiempo Unix en milisegundos
  • 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"]

Consultar la lista completa de archivos y el estado de almacenamiento​

Si no solo se necesitan los archivos disponibles en el servidor, sino todos los archivos conocidos, incluido su estado, puede utilizarse además el endpoint /API/filelist/list/....

Hay que tener en cuenta algunas particularidades:

  • El endpoint devuelve XML en lugar de JSON.
  • La URL utiliza /API/ en mayúsculas.
  • Aquí las indicaciones de tiempo se esperan en segundos, no en milisegundos.

Además, primero se necesita el token del dispositivo. Este puede leerse mediante GET /api/device/{deviceId}/credentials. Allí es relevante el 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"]

Valores de estado posibles:

  • 0: solo en el dispositivo
  • 1: solicitado por el servidor
  • 2: solo en el servidor
  • 3: tanto en el dispositivo como en el servidor
información

Para este endpoint XML debe establecerse un encabezado Accept con compatibilidad con XML. Sin un encabezado adecuado, la plataforma puede responder con 406 Not Acceptable.

nota

Para leer las credenciales de los dispositivos, la cuenta de usuario necesita los derechos adecuados, en particular para mostrar dispositivos y credenciales de dispositivos.

Descargar archivos OSF​

La descarga de archivos se realiza normalmente en dos pasos:

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

En el primer paso se solicita el archivo deseado mediante su nombre de archivo. La respuesta contiene un token de descarga. En el segundo paso se descarga el archivo propiamente dicho con ese 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
información

Si se solicitan varios archivos a la vez, la respuesta suele ser un archivo ZIP y no un único archivo .osfz.

Obtener los últimos datos de telemetría de un dispositivo​

Para obtener valores actuales puede utilizarse GET /api/plugins/telemetry/DEVICE/{device_id}/values/timeseries. En muchos casos, startTs y endTs se establecen simplemente en el momento actual cuando solo se necesitan los valores más recientes.

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

El resultado contiene los valores actuales de cada canal de datos junto con las marcas de tiempo.

Obtener la comunicación del dispositivo​

Los eventos de comunicación de un dispositivo pueden consultarse mediante GET /api/devices/{deviceId}/communication/data. Según el dispositivo y la configuración de la plataforma, esto incluye, por ejemplo, entradas de PING, ATTN o devoluciones de archivos.

Parámetros necesarios:

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

Enviar comandos a un dispositivo​

Algunos endpoints REST sirven para poner en cola comandos para un dispositivo. Normalmente, estos endpoints no establecen una conexión en línea directa con el dispositivo. En su lugar, el comando se procesa en la siguiente conexión del dispositivo.

Un ejemplo es el listado de archivos mediante LIST_FILES. Para ello se utiliza POST /api/devices/{deviceId}/request-list.

El cuerpo de la solicitud contiene normalmente:

  • startTime en formato ISO 8601
  • endTime en formato ISO 8601
  • tag, a menudo por ejemplo 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()

Fundamentos de la MQTT API​

La interfaz MQTT está pensada para dispositivos que se comunican directamente con optiCLOUD. Las tareas típicas son:

  • Enviar telemetría
  • Cargar atributos de cliente
  • Solicitar atributos shared o de cliente
  • Suscribirse a atributos shared

La autenticación se realiza habitualmente mediante el Access Token del dispositivo como nombre de usuario MQTT.

Formato JSON admitido​

La MQTT API utiliza de forma predeterminada un formato clave-valor basado en JSON. Las claves son cadenas de texto; los valores pueden ser, por ejemplo, cadenas de texto, valores booleanos, números o también objetos JSON anidados.

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

Enviar telemetría por MQTT​

Para cargar datos de telemetría se publica en el topic v1/devices/me/telemetry.

Forma sencilla sin marca de tiempo propia:

{"temperature": 42}

Alternativamente también es posible un array de objetos:

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

Si no se envía ninguna marca de tiempo, la plataforma utiliza la marca de tiempo del servidor.

Si el propio dispositivo puede registrar marcas de tiempo, normalmente se utiliza el siguiente formato:

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

Aquí, ts es una marca de tiempo Unix en milisegundos.

Un ejemplo con mosquitto_pub:

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

Sustituya demo.opticloud.io por su host y $ACCESS_TOKEN por el Access Token del dispositivo.

Enviar atributos por MQTT​

Los atributos de dispositivo del lado del cliente se publican en el topic v1/devices/me/attributes.

Ejemplo:

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

Ejemplo con mosquitto_pub:

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

Solicitar atributos al servidor​

Los dispositivos también pueden solicitar activamente al servidor atributos de cliente y shared. Para ello se publica en un topic de solicitud:

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

Al mismo tiempo, el dispositivo debe suscribirse al topic de respuesta correspondiente:

v1/devices/me/attributes/response/+

Como la publicación y la suscripción deben realizarse dentro de la misma sesión MQTT, esto se implementa con frecuencia con una biblioteca de cliente MQTT. Un ejemplo mínimo 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();
});

Para iniciar el ejemplo:

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

Indicaciones para la práctica​

Para las integraciones en producción son importantes sobre todo los siguientes puntos:

  • REST y MQTT tienen responsabilidades distintas y deben combinarse de forma selectiva.
  • Algunos endpoints trabajan con milisegundos y otros con segundos.
  • Para los endpoints XML es importante un encabezado Accept adecuado.
  • Los comandos a dispositivos a menudo solo se registran en una cola y no se ejecutan de inmediato.
  • Para endpoints sensibles, como las credenciales de dispositivos, se necesitan permisos adicionales.

Contexto general​

La API constituye la base técnica para integraciones externas, software de dispositivos, scripts de automatización y evaluaciones específicas de clientes en torno a optiCLOUD.

Para el manejo a través de la interfaz son relevantes en particular las áreas Control Center, Dashboards y Automation. La API complementa estas áreas cuando se desea procesar datos o funciones fuera de la interfaz estándar.