Saltar al contenido principal

Tu primer dispositivo

Al terminar esta página, un dispositivo que creaste habrá enviado una lectura y la estarás viendo en la consola. No necesitas hardware ni firmware. El «dispositivo» es un comando curl, que es todo lo que un dispositivo es visto desde la plataforma.

Calcula alrededor de media hora, la mayor parte esperando al arranque inicial.

Qué da por supuesto esta página

Necesitas dcctl más cinco herramientas en tu PATH: docker, kubectl, helm, kind y OpenTofu (el binario tofu; terraform también sirve). No necesitas un clúster de antemano. Los detalles, y por qué helm está en la lista, están más abajo en Requisitos previos.

Requisitos previos​

dcctl install y dcctl bootstrap ejecutan primero cada uno una comprobación previa, y se detienen si falta alguna de las cinco herramientas. Una carencia te cuesta los diez primeros segundos y no diez minutos.

  • helm es obligatorio. dcctl lleva el chart dentro y lo instala con la biblioteca Go de Helm en vez de con el comando, pero la comprobación previa busca el binario igualmente.
  • ko y cloud-provider-kind son solo advertencias. ko hace falta únicamente para compilar imágenes desde el código (--build).
  • No hace falta un clúster de antemano. dcctl install local busca un clúster de kind llamado devicechain (o el nombre indicado con --cluster) y se ofrece a crear uno si no lo hay. --kube-context <nombre> lo apunta a un clúster que ya operas, y ese nunca lo crea ni lo borra.
  • Kubernetes 1.29 o posterior, en cualquier caso. Las versiones más antiguas se rechazan, porque los charts de la base de datos las rechazan.

dcctl preflight local ejecuta las mismas comprobaciones, y las de tu motor de Docker, los límites del kernel, el disco y los puertos, sin arrancar nada. La guía de arranque inicial tiene el detalle.

Los comandos de abajo suponen que la instancia es alcanzable en localhost por HTTP sin cifrar, que es lo que producen las opciones del paso 1.

1. Levantar una instancia​

Prepara el clúster una vez y después crea la instancia en él:

dcctl install local
dcctl bootstrap local devicechain --host localhost --no-tls
En un script o un trabajo de CI

dcctl bootstrap pide una frase de contraseña para sellar el artefacto de custodia de la clave raíz. Sin un terminal donde preguntarla se detiene con no escrow passphrase and no terminal to ask at. Facilítala sin interacción, con un archivo o con una variable de entorno:

dcctl bootstrap local devicechain --host localhost --no-tls --escrow-passphrase-file ./escrow.pass
# o
DCCTL_ESCROW_PASSPHRASE='<passphrase>' dcctl bootstrap local devicechain --host localhost --no-tls

Para una instancia desechable puedes omitir la custodia con --no-escrow (--dev la implica). El coste: la clave raíz del almacén de secretos existe entonces solo dentro del clúster, así que si el clúster se pierde, todos los secretos que la instancia guardó quedan ilegibles para siempre, incluso desde una copia de seguridad de la base de datos. Consulta la custodia de la clave raíz.

En un clúster kind local, el puerto 1883 de tu máquina llega al broker MQTT de la primera instancia que arranques. Habla TLS; consulta Publicación de un evento para conectarte.

dcctl install crea el clúster de kind e instala lo que comparten todas sus instancias: el operador de DeviceChain y sus definiciones de recurso personalizado, la base de datos relacional, el operador CloudNativePG, cert-manager, la monitorización y el ingress. Se ejecuta una vez por clúster, y dcctl bootstrap se niega en un clúster donde no ha terminado. Consulta Instalar el clúster.

El id de instancia (aquí devicechain) importa en dos sitios:

  • Da nombre al namespace de Kubernetes de la instancia, como el id detrás del prefijo dci- (dci-devicechain).
  • Es el primer segmento de todos los topics de dispositivo y rutas de ingesta de esta página.

Si eliges otro id, sustitúyelo en todas partes: tal cual en los topics y las rutas, y detrás del prefijo dci- allí donde un comando nombre el namespace.

Cuando el arranque termina, imprime el namespace, la URL de la consola y la credencial del superusuario. El superusuario es superuser@devicechain.local. No hay contraseña por defecto: el arranque genera una para esta instancia y la imprime una sola vez, al final de su salida. Para volver a leerla más tarde:

kubectl -n dci-devicechain get secret dci-devicechain-superuser -o jsonpath='{.data.password}' | base64 -d

Ese Secret conserva la contraseña que el superusuario recibió al principio. Si cambias la contraseña en la consola, el Secret no se actualiza.

Abre la consola en http://localhost/ e inicia sesión. Está vacía, porque todavía no hay ningún inquilino y todo dispositivo pertenece a uno.

2. Crear un inquilino​

Crear un inquilino es administración a nivel de instancia. En lugar de recorrer la API de administración a mano, usa el comando que lo hace en un solo paso:

dcctl sim create demo

dcctl sim create se comunica por defecto con la instancia en localhost. Si la consola se sirve en otro puerto, indica host y puerto en --server, y también el ingreso del plano de dispositivos, porque este se deriva del host y no conoce el puerto: dcctl sim create demo --server localhost:8080 --ingress http://localhost:8081.

Este comando:

  • acuña un inquilino sim-demo,
  • crea una identidad demo@sim.devicechain.local limitada a él, con el rol de administrador de inquilino y sin ningún poder sobre la instancia, y
  • escribe un fichero de handshake en ~/.devicechain/sims/demo.json.

Lee la contraseña generada de tu identidad en el fichero de handshake:

cat ~/.devicechain/sims/demo.json

El campo simPassword es la contraseña de demo@sim.devicechain.local. Usarás ambos en el paso siguiente.

Tomado prestado del simulador

dcctl sim create es la primera mitad del flujo del simulador. Se usa aquí porque acuña un inquilino más una identidad limitada, que es exactamente lo que necesitas; hacerlo a mano supone tres mutaciones en la API de administración de la instancia. Todo lo que viene después de este paso es la API de inquilino ordinaria que usa cualquier aplicación.

3. Obtener un token de inquilino​

La autenticación son dos llamadas. La primera demuestra quién eres. La segunda elige en qué inquilino estás actuando, porque una persona puede pertenecer a varios.

curl -s -X POST http://localhost/api/user-management/graphql \
-H 'Content-Type: application/json' \
-d '{"query":"mutation($e:String!,$p:String!){login(email:$e,password:$p){identityToken}}",
"variables":{"e":"demo@sim.devicechain.local","p":"<simPassword del paso 2>"}}'

Eso devuelve un identityToken. Dice quién eres, y nada sobre dónde estás actuando. Intercámbialo por un accessToken con alcance de inquilino:

curl -s -X POST http://localhost/api/user-management/graphql \
-H 'Content-Type: application/json' \
-d '{"query":"mutation($t:String!,$n:String!){selectTenant(identityToken:$t,tenant:$n){accessToken}}",
"variables":{"t":"<identityToken>","n":"sim-demo"}}'

Guarda ese accessToken. Todas las llamadas a partir de aquí lo llevan:

export DC_TOKEN='<accessToken>'

4. Crear el dispositivo​

Los dispositivos son tipados, así que primero creas un tipo de dispositivo. Todo se direcciona por un token que tú eliges, un identificador estable y legible, y no por un id generado.

curl -s -X POST http://localhost/api/device-management/graphql \
-H "Authorization: Bearer $DC_TOKEN" -H 'Content-Type: application/json' \
-d '{"query":"mutation($r:DeviceTypeCreateRequest){createDeviceType(request:$r){token}}",
"variables":{"r":{"token":"temp-probe","name":"Sonda de temperatura"}}}'
curl -s -X POST http://localhost/api/device-management/graphql \
-H "Authorization: Bearer $DC_TOKEN" -H 'Content-Type: application/json' \
-d '{"query":"mutation($r:DeviceCreateRequest){createDevice(request:$r){token}}",
"variables":{"r":{"token":"sensor-001","deviceTypeToken":"temp-probe","name":"Sensor de banco"}}}'

Ahora dale una credencial al dispositivo. La credencial es lo que el dispositivo presenta para demostrar que es él mismo, y la plataforma espera una por defecto.

curl -s -X POST http://localhost/api/device-management/graphql \
-H "Authorization: Bearer $DC_TOKEN" -H 'Content-Type: application/json' \
-d '{"query":"mutation($r:DeviceCredentialCreateRequest!){createDeviceCredential(request:$r){token}}",
"variables":{"r":{"token":"sensor-001-cred","deviceToken":"sensor-001",
"credentialType":"ACCESS_TOKEN",
"credentialId":"5f989616-2a0d-4160-8ae1-da5fad2898b2",
"enabled":true}}}'

Elige tu propio credentialId: cualquier cadena no adivinable. En una credencial ACCESS_TOKEN, el credentialId es el secreto que presenta el dispositivo, así que trátalo como una contraseña y no como un nombre.

Actualiza la lista Dispositivos de la consola. Ahí está sensor-001, todavía sin datos.

5. Abrir una vía hacia el endpoint de ingesta​

El tráfico de dispositivo no entra por la misma puerta que la API. El ingress publica la consola y /api/…. El listener de ingesta de dispositivo es un puerto aparte que una instalación estándar no expone fuera del clúster. Redirige el puerto:

kubectl -n dci-devicechain port-forward svc/event-sources 8081:8081

Déjalo corriendo en su propia terminal.

Por qué existe este paso

Es una propiedad de la instalación por defecto, no de tu configuración. Hacer que el endpoint de ingesta de una flota sea alcanzable públicamente es una decisión que un operador debe tomar a propósito, así que nada la toma por ti. Un despliegue real lo expone deliberadamente; para un solo curl desde tu portátil, una redirección de puerto es lo más sencillo.

6. Enviar una lectura​

Este curl es el dispositivo:

curl -i -X POST http://localhost:8081/devicechain/sim-demo/events \
-H 'Content-Type: application/json' \
-d '{"device":"sensor-001",
"eventType":"Measurement",
"credentialType":"ACCESS_TOKEN",
"credentialId":"5f989616-2a0d-4160-8ae1-da5fad2898b2",
"payload":{"entries":[{"measurements":{"temperature":"21.5"}}]}}'

202 Accepted significa que el evento se encoló. Dos reglas de ese cuerpo atrapan a casi todo el mundo alguna vez:

  • Todo payload envuelve sus lecturas en entries, incluso una sola.
  • Todo valor numérico es una cadena JSON: "21.5", no 21.5. Un número desnudo se rechaza.

La ruta es /{instanceId}/{tenant}/events. devicechain es la instancia del paso 1 y sim-demo es el inquilino del paso 2. Un 404 aquí significa que el id de instancia está mal, porque la ruta solo existe bajo el id propio de esta instancia.

Un inquilino equivocado no devuelve 404. Cualquier nombre de inquilino bien formado se acepta con 202, exista o no un inquilino con ese nombre. El evento se descarta más abajo en la cadena, y nada en la respuesta lo dice. Si un 202 no produce datos, comprueba el nombre del inquilino antes que cualquier otra cosa.

Envía algunas lecturas más con valores distintos, para tener una línea que mirar en vez de un punto:

for t in 21.9 22.4 22.1 23.0; do
curl -s -o /dev/null -X POST http://localhost:8081/devicechain/sim-demo/events \
-H 'Content-Type: application/json' \
-d "{\"device\":\"sensor-001\",\"eventType\":\"Measurement\",
\"credentialType\":\"ACCESS_TOKEN\",
\"credentialId\":\"5f989616-2a0d-4160-8ae1-da5fad2898b2\",
\"payload\":{\"entries\":[{\"measurements\":{\"temperature\":\"$t\"}}]}}"
sleep 1
done

7. Ver tus datos​

En la consola, abre http://localhost/devices/sensor-001. El dispositivo aparece ahora como En línea, con temperature y su último valor. Nada lo declaró en línea: para un dispositivo sobre HTTP, la presencia se infiere del hecho de que llegó un evento.

Por la API, los mismos últimos valores:

curl -s -X POST http://localhost/api/device-state/graphql \
-H "Authorization: Bearer $DC_TOKEN" -H 'Content-Type: application/json' \
-d '{"query":"{latestMeasurements(deviceToken:\"sensor-001\"){name value unit occurredTime}}"}'

Y el histórico en lugar del último valor:

curl -s -X POST http://localhost/api/event-management/graphql \
-H "Authorization: Bearer $DC_TOKEN" -H 'Content-Type: application/json' \
-d '{"query":"{measurementEvents(criteria:{pageNumber:1,pageSize:20,deviceToken:\"sensor-001\"}){results{name value occurredTime} pagination{totalRecords}}}"}'

Ya tienes un dispositivo de principio a fin: registrado, con credencial, reportando y consultable.

Solución de problemas​

Lo que vesNormalmente significa
404 en el POST de ingestaEl id de instancia de la ruta está mal. Es devicechain salvo que lo cambiaras. Un inquilino equivocado no produce esto.
Conexión rechazada en :8081La redirección de puerto del paso 5 no está corriendo.
400 en el POST de ingestaUn número desnudo en vez de una cadena, lecturas no envueltas en entries, o un segmento de inquilino que no es un token válido.
202, pero no aparece nadaO el inquilino no existe (se acepta un nombre bien formado, exista o no ese inquilino), o la credencial no coincidió. El credentialId del cuerpo debe ser exactamente el que creaste en el paso 4.
429 en el POST de ingestaEl inquilino supera su techo de ingesta: estás enviando más lecturas por segundo de las que permite su nivel. El evento no se aceptó. La respuesta lleva una cabecera Retry-After, así que espera y vuelve a enviarlo.
503 con cabecera Retry-After en el POST de ingestaLa plataforma está rechazando eventos nuevos de todos los inquilinos (contrapresión). El evento no se almacenó. Espera los segundos que indica Retry-After (10) y vuelve a enviarlo. curl -i muestra las cabeceras de la respuesta.
503 sin cabecera Retry-After en el POST de ingestaEl evento no pudo entregarse al stream, y puede que se haya almacenado igualmente. Vuelve a enviarlo, pero el reenvío almacena una segunda copia salvo que el evento lleve altId y occurredTime (consulta calidad de servicio).
No autorizado en una llamada a la APIEl token de acceso ha caducado, o estás enviando el identityToken de la primera llamada del paso 3 en vez del accessToken de la segunda.

202, 400 y 404 son terminales para esa petición: no la vuelvas a enviar sin cambios. Reintenta un 429 y cualquiera de los dos tipos de 503, como se describe arriba.

Adónde ir después​

  • Ejecuta una flota simulada. Un dispositivo no es una flota. El dcctl sim create del paso 2 también preparó un escenario simulado. Compila y ejecuta el simulador para que aprovisione un inquilino poblado y emita de forma continua:

    cd backend/sims/dc-simulator && make build
    ./build/dc-simulator --handshake ~/.devicechain/sims/demo.json

    Después contrólalo con dcctl sim status demo, dcctl sim stop demo y dcctl sim start demo. El simulador usa el mismo endpoint de ingesta, así que también necesita la redirección de puerto del paso 5.

  • Conexión de un dispositivo: el transporte real, MQTT, con la credencial en la conexión además de en el evento, más todas las formas de payload y las reglas que impone el pipeline.

  • Matriz de capacidades por transporte: qué admite cada transporte en cada dirección, antes de comprometerte con uno.

  • Envío de un comando: la otra dirección.

  • Procesamiento de eventos: convertir esas lecturas en alarmas.

Limpieza​

dcctl sim destroy demo
dcctl destroy local devicechain

dcctl destroy elimina la instancia y deja el clúster instalado, listo para el siguiente arranque inicial. Espera a que el namespace de la instancia desaparezca por completo, así que el clúster queda listo de inmediato para un arranque con el mismo nombre. Si se interrumpe, ejecutarlo de nuevo termina el trabajo.

Aparta antes el artefacto de depósito

Antes de volver a arrancar con el mismo nombre, aparta el artefacto de depósito que nombra destroy. La siguiente instancia acuña una clave propia, y el arranque inicial no sobrescribe la anterior.

Para eliminar también el clúster:

kind delete cluster --name devicechain