# MQTT

> Dispositivos que publican a través de un bróker MQTT externo, en MQTT simple o Sparkplug B, con el bróker modelado como el nodo que comparten todos los dispositivos que cuelgan de él, y los datos de la propia estación publicados en un bróker.

El controlador MQTT lee y escribe los tags de equipos que publican a través de un **bróker MQTT externo**: una pasarela, un sensor inalámbrico, un flujo de Node-RED, un nodo de borde que habla Sparkplug B. La estación es cliente de ese bróker: se suscribe a los tópicos que leen sus tags y publica las escrituras que usted hace, y cada bróker puede publicar los datos de la propia estación, los tags, las variables de Logic y las alarmas de su [tarjeta Publicar](#the-publish-card), como JSON o en Sparkplug B. Es cliente y no bróker, así que el equipo y la estación se conectan ambos a un bróker: cuando no tiene uno, el [bróker MQTT](connector-mqtt-broker) propio de la estación puede serlo, y encenderlo agrega el bróker **Esta estación** a este controlador; [Instalar un bróker](#installing-a-broker) explica cómo montar uno aparte.

Todos los dispositivos que hay detrás de un bróker usan la misma conexión, por eso la
topología es **Controlador → Bróker → Dispositivo → Tag**: el **bróker** es dueño de dónde
responde y de cómo inicia sesión la estación, escritos una sola vez; cada **dispositivo**
que cuelga de él es dueño solo de cómo publica, **MQTT simple** o **Sparkplug B**, y de sus
tags. Los dos protocolos conviven en el mismo bróker. Los valores llegan por suscripción, no
por sondeo, así que ningún dispositivo ni tag MQTT tiene intervalo de sondeo.

## La página del controlador {#the-drivers-page}

La página del controlador MQTT lista brókeres en lugar de dispositivos. La frase bajo su
cabecera dice "Cada bróker es una conexión a un bróker MQTT externo. Configure dónde responde y cómo inicia sesión la estación, y después añada los dispositivos que publican a través de él."
La tabla **Brókeres MQTT** lleva **Añadir bróker** a la derecha de su título y una fila por
bróker: **Nombre**, **Dirección** (`mqtt://host:port`, o `mqtts://host:port` con TLS, vacía
mientras el bróker no tiene host), **Dispositivos**, **Estado** (un punto y el estado del
bróker con lo que hacen sus dispositivos, como "En línea · 2/2 dispositivos en línea"),
después **Abrir** y, para Configurar Connector, **Habilitar**, **Deshabilitar** y
**Eliminar**. La tabla vacía dice "Todavía no hay brókeres." Un bróker que rechazó la estación lleva el marcador rojo del árbol detrás de su nombre, y la página nombra bajo su cabecera los brókeres con error ("1 bróker con error: Plant").

En el árbol, la fila del controlador cuenta los dos niveles ("1 bróker · 2 dispositivos"), y
la cabecera de la página muestra el número de brókeres entre paréntesis. En el menú Nuevo… de
la columna, la entrada MQTT ofrece **Nuevo bróker** y luego cada bróker con su propio
**Nuevo dispositivo** y sus dispositivos, y hacer clic derecho en la fila del controlador ofrece
**Añadir bróker**. La barra del controlador no tiene Descubrir: una exploración escucha un solo
bróker, así que vive en la página del bróker. Una vez añadido, el controlador queda bajo la
carpeta Controladores del árbol, con bróker o sin él.

## El bróker {#the-broker}

**Añadir bróker**, el **Nuevo bróker** del menú Nuevo… y el menú contextual de la fila del
controlador crean un bróker llamado "Nuevo bróker" (después "Nuevo bróker 2") y
deshabilitado, para que su conexión se rellene antes de que la estación se conecte. El registro
de acciones lo dice: "Se añadió el bróker 'Nuevo bróker' deshabilitado. Indique dónde responde y después habilítelo."
El bróker no se abre: queda resaltado en la tabla Brókeres MQTT de la página del controlador, adonde va la pantalla desde Nuevo… o el clic derecho. Su página, a la que se llega con el **Abrir** de su fila o con su fila en el árbol, lleva la frase "Un bróker MQTT: dónde responde y cómo inicia sesión la estación en él. Todos los dispositivos que cuelgan de él comparten esta conexión."
y, en la cabecera, el estado, su punto y la dirección. Bajo la cabecera se nombran los dispositivos del bróker que tienen un error propio ("1 dispositivo con error: Pump"); mientras el propio bróker tiene un error, sus dispositivos señalan hacia él: la página de un dispositivo dice "Este dispositivo pasa por el bróker Plant, que tiene un error. La página del bróker dice por qué."

### La tarjeta Conexión con el bróker {#the-broker-connection-card}

| Campo | Significado | Por defecto |
| --- | --- | --- |
| **Nombre** | El nombre descriptivo del bróker en el árbol y en cada dispositivo que cuelga de él. Nunca entra en una dirección. En blanco se rechaza: "El nombre del bróker es obligatorio." | "Nuevo bróker" |
| **Host o IP** | El nombre de host o la dirección IP del bróker. Se puede pegar una dirección entera: `mqtt://` o `tcp://` delante ponen el transporte en claro, `mqtts://`, `ssl://` o `tls://` con TLS, un puerto tras el host pasa a **Puerto**, y el host se guarda limpio. Otro esquema, una ruta o un inicio de sesión escritos en la dirección se rechazan bajo la casilla, y cada campo conserva lo que tenía: "'http://192.168.0.20' no es una dirección de bróker. Escriba el nombre de host o la dirección IP del bróker, opcionalmente como mqtt://host:port o mqtts://host:port." | vacío |
| **Puerto** | El puerto TCP en el que responde el bróker, de 1 a 65535. | 1883 |
| **Transporte** | La casilla **Usar TLS**. Marcada, la conexión va cifrada y el certificado del bróker se comprueba contra el almacén de certificados de Windows o contra los certificados aprobados en [Acceso a los equipos](#equipment-access): "Cifrado: Windows tiene que confiar en el certificado del bróker, o este tiene que estar aprobado en Acceso a los equipos, en la página del controlador MQTT." Desmarcada, la nota avisa "Sin cifrar: los valores y la contraseña viajan en claro." Cambiarla lleva un puerto que se quedó en 1883 a 8883, y de vuelta; cualquier otro puerto se queda como está. | desmarcada |
| **Nombre de usuario**, **Contraseña** | La cuenta con la que la estación inicia sesión en el bróker. Un nombre de usuario vacío inicia sesión de forma anónima. La contraseña se protege en reposo como las credenciales de un dispositivo y nunca aparece en una dirección, en el diario ni en la respuesta de un agente. | vacío |
| **ID de cliente** | El nombre que lleva la sesión de la estación en el bróker, de 1 a 128 caracteres sin caracteres de control. Un bróker mantiene una sesión por ID de cliente y expulsa la más antigua de dos, así que un ID de cliente pertenece a un solo bróker de esta estación: "El ID de cliente 'GanterLab01' ya pertenece al bróker 'Plant'. Dos sesiones con el mismo ID de cliente se expulsan mutuamente del bróker: elige otro." Cuando dos estaciones comparten un bróker, cada una conserva el suyo. | `GanterLab` y 14 dígitos hexadecimales, generado |
| **Intervalo keep-alive (s)** | Cada cuánto demuestra la sesión que sigue viva cuando no circula nada más: "El keep-alive debe estar entre 5 y 3600 segundos." | 60 |

Bajo los campos, una frase dice qué están haciendo el bróker y sus dispositivos, qué le
falta todavía al bróker, y "Todos los dispositivos bajo este bróker y lo que la estación publica en él comparten esta conexión: la sesión se abre cuando el primer dispositivo habilitado o la primera fila publicada la necesita."
Todos los campos siguen editables con el bróker habilitado. Un cambio guardado de dónde
responde, del transporte, del inicio de sesión, del ID de cliente o del keep-alive reconecta
la única sesión que comparten todos los dispositivos del bróker.

### La barra de comandos {#the-command-bar}

| Comando | Qué hace | Gris cuando (situación) | No se dibuja cuando (rol) |
| --- | --- | --- | --- |
| **Habilitar** | Deja que los dispositivos del bróker usen su sesión. Un bróker sin host se rechaza: "Introduzca el nombre de host o la dirección IP del bróker antes de habilitarlo." | Ya está habilitado, la conexión no está completa ("La conexión de este bróker todavía no está completa."), hay una exploración en marcha, o la estación rechaza los cambios. | Sin Configurar Connector. |
| **Deshabilitar** | Cierra la sesión para todos los dispositivos del bróker; cada dispositivo conserva su propia elección de Habilitado y vuelve como estaba cuando el bróker se habilita de nuevo. | Ya está deshabilitado, hay una exploración en marcha, o la estación rechaza los cambios. | Sin Configurar Connector. |
| **Eliminar el bróker** (tinta de peligro, a la derecha) | Pregunta primero. Un bróker vacío: "Este bróker no tiene dispositivos."; uno con dispositivos: "Esto también elimina los 2 dispositivos del bróker y todos sus tags." Después queda seleccionado el controlador MQTT y el registro de acciones dice "Se eliminó el bróker 'Plant' y sus 2 dispositivos." | Hay una exploración en marcha ("Una exploración de descubrimiento está usando este bróker. Espere a que termine."), o la estación rechaza los cambios. | Sin Configurar Connector. |

Hacer clic derecho en la fila de un bróker ofrece **Añadir dispositivo**, **Habilitar**,
**Deshabilitar** y **Eliminar**.

### Estados del bróker {#broker-states}

El bróker informa de su sesión aparte de sus dispositivos, en la fila del árbol, en la tabla
de brókeres del controlador y en su propia cabecera:

| Estado | Punto | Significado |
| --- | --- | --- |
| **Apagado** | gris | El bróker está deshabilitado. |
| **Sin sesión** | gris | Habilitado, pero ningún dispositivo habilitado lo usa y su tarjeta Publicar no publica nada, así que no se abre ninguna sesión. |
| **Conectando…** | ámbar | Hay un dispositivo habilitado, o el bróker publica, y la sesión se está conectando, o reconectando tras una pérdida. |
| **En línea** | verde | La sesión está conectada. |
| **Inaccesible** | rojo | No se pudo alcanzar el bróker o rechazó a la estación. La causa se escribe en rojo bajo la cabecera en el idioma de la estación y, con las palabras del controlador, como información sobre la marca de error de la fila, en términos de qué corregir: nada responde en ese puerto, el nombre no se conoce en esta red, se rechazaron el nombre de usuario o la contraseña, el certificado no es de confianza o espera aprobación en [Acceso a los equipos](#equipment-access), otro cliente se quedó con la sesión con el mismo ID de cliente. Un bróker que rechaza el puerto o un cliente sin nombre de usuario se dice con su ajuste (vea [Preparar el bróker](#prepare-the-broker)). |

La fila del árbol muestra el estado y el número de dispositivos ("En línea · 2 dispositivos"),
y el resumen bajo la tarjeta añade qué hacen los dispositivos:
"En línea · 2/2 dispositivos en línea", "En línea · 1 en línea · 1 con problemas",
"Sin sesión · sin dispositivos habilitados". Un dispositivo Sparkplug B que anunció su muerte
solo se saca a sí mismo del aire: el bróker sigue En línea. La sesión habla MQTT 5 y recurre
a MQTT 3.1.1 con un bróker que no lo habla, empieza limpia y se reconecta sola, esperando
cada vez más entre intentos mientras el bróker sigue ausente.

### La tarjeta Descubrir dispositivos {#the-discover-devices-card}

Se dibuja solo para Configurar Connector, bajo la frase "Escucha en este bróker durante un rato: los nodos de borde y dispositivos Sparkplug B que se anuncian, y los tópicos bajo un filtro."

| Campo | Significado | Por defecto |
| --- | --- | --- |
| **Escuchar (s)** | Cuánto tiempo escucha la exploración, de 3 a 60 segundos. | 10 |
| **Filtro de tópicos** | Los tópicos simples que listar, como tags de un dispositivo. Se admiten los comodines `+` (un nivel) y `#` (este nivel y todo lo que hay debajo, solo al final), de ahí "p. ej., plant/#". Vacío lista solo dispositivos Sparkplug B. | vacío |

**Descubrir** inicia la exploración y se convierte en **Cancelar** mientras dura. La frase
bajo los campos dice qué va a hacer ("Escucha 192.168.0.20:1883 durante 10 s."), y la frase
de progreso cuenta la ventana ("Escuchando 192.168.0.20:1883 · 4 de 10 s…"). La exploración
necesita un host guardado en el bróker: hasta entonces la tarjeta dice "Introduzca el host del bróker y deje que se guarde la edición antes del descubrimiento."
Una edición pendiente en el bróker se guarda primero; si no se puede, no empieza nada:
"El descubrimiento no se inició porque no se pudo guardar la edición del bróker."

Lo que hace una exploración en el bróker:

- Se conecta con un ID de cliente propio, así que nunca expulsa del bróker la sesión de los
  dispositivos, y puede ejecutarse con el bróker habilitado y en uso.
- Se suscribe a todos los tópicos Sparkplug B (`spBv1.0/#`) y al filtro de tópicos. Los
  certificados de nacimiento de Sparkplug B no se retienen, así que a un nodo de borde oído
  sin uno se le pide que se anuncie de nuevo (el comando `Node Control/Rebirth`, el único
  mensaje que publica la exploración); un nodo de borde en marcha responde dentro de la
  ventana.
- Cada nodo de borde y cada dispositivo detrás de él que se anuncia (NBIRTH, DBIRTH) se
  convierte en un candidato con sus métricas como tags, tipadas según lo que declara el
  nacimiento. La fontanería propia del nodo de borde (`bdSeq` y las métricas
  `Node Control/…` y `Device Control/…`) queda fuera, igual que las métricas que ningún tag
  lee (matrices, conjuntos de datos, plantillas, conjuntos de propiedades, bytes, archivos),
  contadas por su motivo. Un nodo de borde oído pero no nacido dentro de la ventana se ofrece
  sin tags.
- Con un filtro de tópicos, los tópicos simples oídos bajo él se convierten en un candidato
  propio: cada tópico es un tag o, en una carga útil JSON, cada valor que contiene lo es por
  su ruta del valor (hasta 32 por tópico, cuatro niveles de profundidad, los 8 primeros
  elementos de una matriz). Un número se ofrece como Double, `true` o `false` como Boolean,
  cualquier otra cosa como String. La lista se detiene en 500 tópicos. Un tópico cuyo
  publicador retiene su último mensaje se oye enseguida; cualquier otro, solo si publica
  durante la ventana.
- No se guarda nada hasta que usted añade un candidato. Cancelar termina la ventana al
  instante.

La tarjeta de resultados, **Dispositivos descubiertos**, lista **Publica como**
(`group/edge node`, con `/device` detrás de un nodo de borde, o "Tópicos simples"),
**Dispositivo** (el nombre con el que añadirlo, editable: el ID del dispositivo o del nodo de
borde, o los niveles del filtro antes de su primer comodín), **Resultado**
("12 de 12 tags seleccionados") y **Añadir dispositivo**, que crea el dispositivo en este
bróker con esos tags. Una identidad Sparkplug B ya configurada en el bróker dice
"Ya configurado" y ofrece **Abrir** en su lugar. Una exploración que no oyó nada dice
"Nada se anunció en este bróker mientras la exploración escuchaba."

Un dispositivo que ya tiene encuentra lo que todavía no lee desde su propia página: **Descubrir tags**, en la cabecera de su tarjeta de tags, escucha 10 s en su bróker. Un dispositivo Sparkplug B oye solo su propio nodo de borde, al que se le pide su nacimiento mientras a ningún otro nodo de borde se le pide nada; un dispositivo simple oye los topics bajo los niveles que comparten sus tags (`plant/oven/#` para `plant/oven/temperature` y `plant/oven/count`), y todos los topics del bróker cuando no comparten ninguno. Solo se ofrecen los valores para los que todavía no tiene tag (vea [Descubrir los tags de un dispositivo](connector-discovery#discovering-a-devices-tags)).

### La tarjeta Dispositivos {#the-devices-card}

"Cada dispositivo lee tópicos simples, o un nodo de borde o dispositivo Sparkplug B. La conexión se hereda del bróker y aquí no se puede cambiar."
**Añadir dispositivo** crea "Nuevo dispositivo" en este bróker, habilitado, en MQTT simple,
resaltado en esta tabla y desplazado hasta quedar a la vista: "Se añadió el dispositivo 'Nuevo dispositivo' en 'Plant'." La tabla lista
**Dispositivo**, **Publica como**, **Tags** y **Estado de salud**, con Abrir,
Habilitar, Deshabilitar y Eliminar en cada fila; vacía, dice "No hay dispositivos configurados en este bróker." Un dispositivo con un error propio lleva el marcador rojo del árbol detrás de su nombre.

### La tarjeta Publicar {#the-publish-card}

La última tarjeta publica los datos de la propia estación en este bróker, bajo la frase "La estación publica cada fila en este bróker como JSON en su tópico cada vez que cambia el valor o su calidad, y de nuevo todas las filas cada vez que se abre la conexión. El bróker guarda el último mensaje de cada tópico para quien se suscriba después. La estación solo se suscribe al tópico de escritura de cada fila en Lectura y escritura."
Quien consulta el Connector lee la tarjeta; quien lo configura la cambia. El bróker Esta estación
del [bróker MQTT](connector-mqtt-broker) propio de la estación tiene la misma tarjeta.

| Campo | Significado | Por defecto |
| --- | --- | --- |
| **Formato de publicación** | Cómo salen las filas: **JSON** en el tópico de cada fila ([Publicación](#publishing)), o **Sparkplug B**, la estación como un nodo de borde del bróker ([Publicación en Sparkplug B](#publishing-in-sparkplug-b)). Su información emergente: "Cómo salen las filas: JSON en el tópico propio de cada fila, o Sparkplug B, con esta estación como un nodo de borde que Ignition y otros consumidores de Sparkplug B encuentran por su cuenta." | JSON |
| **Prefijo del tópico** | Solo en JSON. El nivel bajo el que se propone el tópico de cada fila nueva. Vacío, el campo muestra el valor por defecto y lo usa. "El prefijo del tópico no puede llevar + ni #, empezar por $ ni tener un carácter que ningún tópico puede tener." Cambiarlo deja como están los tópicos que ya están en la lista. | `ganterlab/` y el nombre de la estación |
| **ID de grupo** | Solo en Sparkplug B, en lugar del prefijo. El grupo Sparkplug B del nodo de borde de la estación en este bróker; vacío, es `GanterLab`. Un grupo con `/`, `+` o `#` se rechaza bajo el campo, y el bróker no se guarda hasta que se corrija. | `GanterLab` |

En Sparkplug B, la frase bajo el título dice en su lugar "La estación es un nodo de borde Sparkplug B en este bróker: las variables y las alarmas del Logic son sus propias métricas, y cada dispositivo con filas es un dispositivo Sparkplug B. Anuncia todas las métricas cuando se abre la conexión y cada vez que cambia la lista, y después publica cada valor o calidad que cambia. Solo una fila en Lectura y escritura acepta un valor de un comando.", y bajo los campos **ID del nodo de borde (el nombre de la estación)** muestra el nodo de borde con el que publica.

La tabla lista **Fuente**, **Tópico** y **Acceso**, por tópico; una fila cuya fuente todavía no
se ha elegido dice "Todavía sin fuente". Sin ninguna, dice "Todavía no se publica nada en este bróker. Añada una fila con Nueva fila."
En Sparkplug B, la columna **Tópico** es **Métrica**: un tag tras su dispositivo (`Oven · Temperature`), una variable o una alarma por su nombre en el nodo de borde (`Logic/Oven/Setpoint`, `Alarms/High`).
Con el bróker deshabilitado y filas en la lista, una nota dice "El bróker está deshabilitado, así que no se publica nada hasta que se habilite."
Con una fila del bróker en Lectura y escritura, una segunda nota dice "Las filas en Lectura y escritura aceptan los valores publicados en su tópico de escritura. Quien pueda publicar en este bróker puede cambiar esas fuentes, así que el nombre de usuario y la contraseña del bróker son su única protección." En Sparkplug B dice "Las filas en Lectura y escritura aceptan valores de comandos Sparkplug B (NCMD, DCMD). Quien pueda publicar en este bróker puede cambiar esas fuentes, así que el nombre de usuario y la contraseña del bróker son su única protección."

| Comando | Qué hace | Gris cuando (situación) | No se dibuja cuando (rol) |
| --- | --- | --- | --- |
| **Nueva fila** | Añade una fila que todavía no publica nada y la selecciona: "Fila añadida a la lista Publicar del bróker. Elija su fuente." | Un ensayo retiene la estación, o el runtime está detenido. | Sin Configurar Connector. |
| **Eliminar fila** (tinta de peligro) | Elimina al momento la fila seleccionada; su información emergente nombra lo que publica. La estación deja de publicarla; el mensaje que el bróker guardó en su tópico sigue allí. | No hay ninguna fila seleccionada ("Seleccione una fila de la lista para eliminarla."), un ensayo retiene la estación, o el runtime está detenido. | Sin Configurar Connector. |

Los campos de una fila se guardan en cuanto cambian:

| Campo | Qué hace |
| --- | --- |
| **Fuente** | Lo que publica la fila: "(ninguno)", luego cada tag habilitado de un dispositivo habilitado ("Tag del Connector · Simulated · Oven · Temperature"), cada variable de Logic que guarda un valor y cada alarma, nombradas por sus carpetas. Un comando de dispositivo no publica nada y no se ofrece. La fila guarda su fuente por su identidad, así que un cambio de nombre la deja en ella, y la tarjeta [Usado por](connector-tags#the-used-by-card) de la fuente lista la fila. |
| **Tópico** | Solo en JSON. Dónde publica la fila. Elegir una fuente propone un tópico a partir de su nombre: `<prefix>/<device>/<tag>` para un tag, `<prefix>/logic/<folders>/<variable>` para una variable, `<prefix>/alarms/<folders>/<alarm>` para una alarma, cada nombre un nivel de tópico (un espacio, una barra o un comodín se convierte en `_`). Edítelo libremente: un tópico que sigue siendo el propuesto sigue a una fuente elegida después, uno que usted escribió se queda, y renombrar la fuente lo mantiene. |
| **Acceso** | **Solo lectura**, el valor por defecto de toda fila: la estación publica la fila y no se suscribe a nada por ella. **Lectura y escritura**: la estación también acepta los valores publicados en el tópico de escritura de la fila, su tópico seguido de `/set`, y los escribe en la fuente ([Escritura de vuelta](#writing-back)). Solo se ofrece para un tag con acceso de escritura en un controlador que escribe, o una variable de memoria; en las demás fuentes sigue en la lista, en gris: "Esta fuente no acepta escrituras: solo las aceptan un tag con acceso de escritura o una variable de memoria." Elegir una alarma devuelve la fila a solo lectura. En Sparkplug B, Lectura y escritura acepta, en lugar del tópico de escritura, los valores que un comando Sparkplug B envía a la métrica de la fila. |

Bajo los campos, una frase dice qué publica la fila:

| La tarjeta dice | Cuándo |
| --- | --- |
| "Esta fila todavía no publica nada: elija su fuente." | No se ha elegido ninguna fuente. |
| "Publica el valor, su calidad y su hora como JSON cada vez que cambia el valor o la calidad." | Un tag o una variable, de solo lectura. |
| "Publica el valor, su calidad y su hora como JSON cada vez que cambia el valor o la calidad, y escribe en la fuente cada valor publicado en el tópico de escritura: cada escritura queda en el diario técnico con el nombre del bróker." | Un tag o una variable en Lectura y escritura. |
| "Publica como JSON si la alarma está activa y reconocida, su estado, prioridad, mensaje y hora cada vez que cambia alguno de ellos." | Una alarma. |
| "La fuente de esta fila ya no existe, así que ya no se publica nada por ella. El bróker conserva el último mensaje que guardó." | Se eliminó el tag, la variable o la alarma. |
| "Publica el valor y su calidad en Sparkplug B cada vez que cambia el valor o la calidad." | Sparkplug B: un tag o una variable, de solo lectura. |
| "Publica el valor y su calidad en Sparkplug B cada vez que cambia el valor o la calidad, y escribe en la fuente cada valor que un comando Sparkplug B envía a su métrica: cada escritura queda en el diario técnico con el nombre del bróker." | Sparkplug B: un tag o una variable en Lectura y escritura. |
| "Publica en Sparkplug B si la alarma está activa y reconocida, su estado, prioridad y mensaje cada vez que cambia alguno de ellos." | Sparkplug B: una alarma. |
| "La fuente de esta fila ya no existe, así que el nodo de borde se anuncia de nuevo sin ella." | Sparkplug B: se eliminó el tag, la variable o la alarma. |

Bajo esa frase, una fila en Lectura y escritura muestra su **Tópico de escritura**, el tópico de la fila seguido de `/set`.
En Sparkplug B no hay tópico de escritura: una segunda frase dice la métrica con la que se publica la fila, de su dispositivo Sparkplug B para un tag, del nodo de borde de la estación para una variable y, para una alarma, las cinco métricas bajo su nombre (Active, Acked, State, Priority y Message).

Un cambio que la lista rechaza no se guarda: el motivo se escribe en rojo encima de los campos
de la fila, y el cambio queda en la pantalla para que usted lo corrija.

| La tarjeta dice | Cuándo |
| --- | --- |
| "Esta fuente ya se publica en este bróker: edite la fila que la publica." | Otra fila de este bróker publica la fuente. |
| "Otra fila de este bróker ya publica en este tópico: dé a esta fila un tópico propio." | Dos filas de un bróker publicarían en un mismo tópico. |
| "No se puede publicar nada en un filtro: el tópico no puede llevar + ni #." | El tópico lleva un comodín. |
| "Un tópico que empieza por $ pertenece al bróker: escriba otro." | El tópico empieza por `$`. |
| "Este tópico no se puede enviar: tiene un carácter que ningún tópico puede tener, o es demasiado largo." | Un carácter de control, o más de lo que lleva un tópico MQTT. |
| "Una fila que acepta escrituras necesita el tag o la variable a la que van." | Se eligió Lectura y escritura antes que una fuente. |
| "La fuente no se puede escribir: elija un tag con acceso de escritura o una variable de memoria, o deje la fila de solo lectura." | Lectura y escritura en una alarma, una variable de expresión, un tag de solo lectura o un tag cuyo controlador no escribe. |
| "Este tópico y el tópico de otra fila de este bróker solo difieren en /set, donde una fila acepta escrituras: dé otro tópico a esta fila." | El tópico de una fila es el tópico de otra seguido de `/set`. |
| "Esta fila de la lista ya no existe. Recargue y vuelva a intentarlo." | La fila se eliminó en otro lugar mientras usted la editaba. |

Qué se publica, y cuándo, está en [Publicación](#publishing).

## Dispositivo {#device}

| Campo | Significado | Por defecto |
| --- | --- | --- |
| **Bróker · fijo** | El nombre del bróker sobre su dirección, de solo lectura: un dispositivo se crea en su bróker y se queda en él. "El bróker todavía no tiene host" ocupa el lugar de la dirección. "Falta el bróker" significa que el dispositivo perdió su bróker y no se puede guardar: "Se necesita un bróker MQTT." | el bróker en el que se añade |
| **Protocolo** | Cómo publica el dispositivo. **MQTT simple**: "Cada tag nombra el tópico que lee y, para una carga útil JSON, la ruta hasta su valor." **Sparkplug B**: un nodo de borde, o un dispositivo detrás de él: "Cada tag nombra una métrica tal como la declara el certificado de nacimiento del nodo de borde; su tipo llega con la métrica." Cambiarlo vuelve a comprobar cada tag, ya que una dirección pasa a leerse como tópico o como métrica. | MQTT simple |
| **Tiempo máximo sin mensaje (s)** | Solo MQTT simple, de 0 a 86400, donde 0 significa nunca: "Los tags dejan de contar como lecturas cuando no llega ningún mensaje a su tópico durante este tiempo. 0: nunca, para un tópico que solo se publica cuando cambia su valor. Ajústalo para un dispositivo que publica a un ritmo fijo, como un sensor que informa cada pocos segundos." Fuera de ese rango el dispositivo no se guarda: "El tiempo máximo sin mensaje debe estar entre 0 y 86400 s." Un dispositivo Sparkplug B no lo tiene: su certificado de muerte ya dice cuándo desapareció. | 0 |
| **ID de grupo** | Solo Sparkplug B: el grupo bajo el que publica el nodo de borde, el nivel que sigue a `spBv1.0` en sus tópicos. "Un dispositivo Sparkplug B necesita su ID de grupo." | vacío |
| **ID del nodo de borde** | Solo Sparkplug B: el nodo de borde que publica las métricas, tal como lo nombra su NBIRTH. "Un dispositivo Sparkplug B necesita su ID de nodo de borde." | vacío |
| **ID del dispositivo** | Solo Sparkplug B: el dispositivo detrás del nodo de borde, tal como lo nombra su DBIRTH. Vacío, mostrado como "El propio nodo de borde", lee las métricas propias del nodo de borde. | vacío |

El dispositivo no tiene host, puerto, credenciales ni intervalo de sondeo propios: todo eso
es del bróker. Cada ID de Sparkplug B es un solo nivel de tópico: "Un ID de Sparkplug B no puede contener '/', '+' ni '#'."
Un bróker nombra un nodo de borde o dispositivo Sparkplug B una sola vez, así que un segundo
dispositivo con los mismos ID de grupo, nodo de borde y dispositivo en ese bróker se rechaza:
"El dispositivo Sparkplug B Plant/Line1/Press1 del bróker 'Plant' ya pertenece al dispositivo 'Press'."
Los dispositivos de MQTT simple no nombran nada propio en el bróker, y dos de ellos pueden
leer los mismos tópicos.

La Base URI que muestra la cabecera es la dirección de runtime:
`mqtt://<broker id>?host=…&port=…&tls=0&keepAlive=60&clientId=…&mode=plain`, con `&silence=…` cuando el dispositivo fija un tiempo máximo sin mensaje, y para un
dispositivo Sparkplug B `mode=sparkplug&group=…&node=…`, con `&device=…` detrás de un nodo
de borde. Nombra el bróker por su identidad estable, nunca por su nombre descriptivo, y nunca
lleva el nombre de usuario ni la contraseña.

La copia de un dispositivo MQTT se queda en el mismo bróker. Una copia Sparkplug B toma la
primera identidad que no tenga ningún dispositivo de ese bróker: su ID más interno, el ID del
dispositivo o, para las métricas propias del nodo de borde, el ID del nodo de borde, se
numera como el nombre de la copia ("Press1 (2)").

## Direccionamiento del tag {#tag-addressing}

El campo de dirección es **Tópico** en un dispositivo de MQTT simple y **Métrica** en uno
Sparkplug B, y es obligatorio ("Tópico es obligatorio para este controlador."). Un tag simple
tiene tres campos más; una métrica Sparkplug B no tiene ninguno, porque lleva su propio tipo
y se escribe con el comando del propio protocolo.

| Campo | Significado | Por defecto |
| --- | --- | --- |
| **Tópico** | El único tópico que lee el tag, p. ej. `plant/line1/temperature`. "Un tag lee un solo tópico, así que su tópico no puede llevar los comodines + ni #." | obligatorio |
| **Ruta del valor** | Dónde está el valor dentro de una carga útil JSON: miembros separados por puntos e índices de matriz entre corchetes, p. ej. `sensors[0].value`. Un miembro cuyo nombre lleva un punto o un corchete se escribe entre comillas dentro de corchetes (`['flow.rate']`), y se acepta un `$.` inicial. Vacía, mostrada como "Toda la carga útil", lee toda la carga útil como el valor. Una ruta mal formada: "Escriba la ruta del valor como miembros separados por puntos e índices de matriz entre corchetes, p. ej., sensors[0].value." | vacía |
| **Tópico de comando** | Dónde se publica una escritura. Vacío deja el tag de solo lectura: un tag cuyo Acceso escribe necesita uno ("Un tag que se escribe necesita el tópico en el que se publican sus escrituras."), y no lleva comodines ("No se puede publicar nada en un filtro: el tópico de comando no puede llevar + ni #."). Un equipo rara vez escucha en el tópico en el que informa, y por eso este es un campo aparte. | vacío |
| **Plantilla de carga útil** | Lo que publica una escritura: JSON con `{value}` una vez, fuera de comillas, donde va el valor escrito, p. ej. `{"setpoint": {value}}`. Vacía, mostrada como "El valor como texto", publica el valor como texto. "La plantilla de carga útil contiene {value} exactamente una vez, donde va el valor escrito." / "Escriba la plantilla de carga útil como JSON, con {value} fuera de comillas donde va un valor JSON." | vacía |
| **Métrica** | Sparkplug B: el nombre de la métrica tal como lo declara el certificado de nacimiento, p. ej. `Inputs/Temperature`. | obligatorio |

Los tipos de datos son Boolean, Int16, Int32, Int64, Float, Double, String y DateTime. La
vista previa de solo lectura de la dirección en el cable es el tópico seguido de lo que
aplique de `path`, `command` y `payload`
(`plant/line1/temperature?path=sensors[0].value&command=plant/line1/setpoint`), o el nombre
de la métrica; un carácter que terminaría una parte (`%`, `?`, `&`, `=`, `#`, `+`, un
espacio) se escribe como `%XX`.

## Lectura {#reading}

La sesión guarda el último mensaje de cada tópico al que se suscribió, y un tag lee de él:

| Situación | Tag de MQTT simple | Tag Sparkplug B |
| --- | --- | --- |
| El valor | El último mensaje del tópico: toda la carga útil como texto, o el valor JSON en la ruta del valor, convertido al tipo del tag. | El valor de la métrica en el último mensaje de nacimiento o de datos del nodo de borde. |
| Su marca de tiempo | Cuándo llegó el mensaje. Un mensaje que el publicador retuvo da el primer valor en cuanto la estación se suscribe. | La marca de tiempo de la propia métrica. |
| Nada recibido todavía | El tag espera: sin valor y sin error, como con cualquier controlador antes de su primera respuesta. Un tópico cuyo publicador no retiene no tiene valor hasta que vuelve a publicar. Con un **Tiempo máximo sin mensaje (s)**, la espera dura ese tiempo desde que la estación empezó a escuchar, y luego el tag falla su lectura. | Lo mismo hasta que llega el certificado de nacimiento; la estación pide al nodo de borde que se anuncie de nuevo. |
| El publicador se calla, el bróker sigue disponible | Con un **Tiempo máximo sin mensaje (s)**, un tag cuyo tópico lleva más tiempo sin mensajes falla su lectura, y la política del tag para [un dispositivo que no respondió](connector-value-pipeline#the-device-did-not-answer) decide qué muestra; el siguiente mensaje lo devuelve. El tiempo cuenta desde la llegada del último mensaje, también uno retenido. Con 0 el tag conserva el último valor que recibió. | No se aplica: el certificado de muerte lo dice. |
| Un mensaje que el tag no puede leer | Una lectura fallida de ese tag, con el motivo: un mensaje vacío, una carga útil que no es JSON bajo una ruta del valor, nada en la ruta, o un valor que el tipo no admite. | Una lectura fallida de ese tag: una métrica que el certificado de nacimiento no declara, o un valor que su tipo no admite. |
| El bróker inaccesible | Todos los dispositivos del bróker quedan desconectados, y sus tags fallan como los de cualquier dispositivo. | Lo mismo. |
| Un certificado de muerte (NDEATH, DDEATH) | No aplica. | El nodo de borde y sus dispositivos, o ese único dispositivo, se quedan sin valor hasta que vuelven a nacer; todos los demás dispositivos del bróker siguen. |

Un tag Boolean admite `true` o `false`, `1` o `0`, `on` u `off`. Un tag entero admite un
número fraccionario redondeado y rechaza uno fuera de su rango. Un tag DateTime admite texto
ISO 8601 o milisegundos Unix. Un tag String admite el texto tal cual, y un objeto o una matriz
JSON como su JSON en bruto. Una métrica Sparkplug B de un tipo sin signo se lee en el tipo con
signo inmediatamente más ancho (UInt8 como Int16, UInt16 como Int32, UInt32 y UInt64 como
Int64), para que se lea todo su rango.

## Escritura {#writing}

Una escritura pasa por el mismo embudo que la de cualquier otro controlador: la casilla de
escritura recibe el valor de ingeniería, y las etapas de conversión se invierten antes de
publicar el valor en bruto (vea [Connector](connector)).

| Tag | Qué se publica | QoS | Qué informa la escritura |
| --- | --- | --- | --- |
| MQTT simple | El valor en el **Tópico de comando** del tag, como texto (`21.5`, `true`, el propio texto, una fecha en ISO 8601), o puesto en la **Plantilla de carga útil** como valor JSON (un número como número, un booleano como `true` o `false`, un texto entre comillas y escapado). | 1, sin retener | Hecha cuando el bróker la confirma; rechazada, con el motivo del bróker, cuando un bróker MQTT 5 la rechaza; sin respuesta cuando no llega ninguna confirmación a tiempo. |
| Sparkplug B | Un NCMD (una métrica del nodo de borde) o un DCMD (una métrica de un dispositivo detrás de él) que lleva la métrica, con el alias que le dio su nacimiento y con el tipo que declaró su nacimiento. | 0, sin retener, como exige la especificación Sparkplug B para los comandos | Hecha en cuanto se envía. El nodo de borde responde publicando el valor nuevo, que es lo que el tag lee a continuación. |

Mientras el bróker está inaccesible, o el nodo de borde o el dispositivo ha anunciado su
muerte, una escritura se rechaza como desconectada y no se envía nada. Una escritura Sparkplug
B en una métrica que el certificado de nacimiento no declara se rechaza, ya que el nodo de
borde no la aceptaría, y lo mismo ocurre con un valor que el tipo de la métrica no puede
llevar. Una escritura nunca se retiene: un punto de consigna retenido se aplicaría de nuevo
por cada suscriptor que se conectara después.

## Publicación {#publishing}

Esta sección describe el **Formato de publicación** en **JSON**; el Sparkplug B tiene la sección siguiente.

Cada fila de la [tarjeta Publicar](#the-publish-card) de un bróker se publica en su tópico,
retenida, con QoS 1, por la conexión que usan los dispositivos del bróker: la estación nunca abre
una segunda conexión con un bróker. Un bróker que solo se usa para publicar no necesita ningún
dispositivo. Queda bajo el controlador MQTT, y su estado (Conectando…, En línea, Inaccesible) es el
de esa conexión. Publica mientras está habilitado y la lista tiene filas; no tiene interruptor propio.

| Fuente | Mensaje (JSON) |
| --- | --- |
| Tag | `{"value": 21.5, "quality": "good", "timestamp": "2026-10-04T12:30:15.250Z"}`: el valor de ingeniería que muestra el tag; `quality` es `good` para una lectura que llegó, `bad` con `value` nulo para una que no llegó, y `uncertain` para un valor de sustitución, para el último valor mantenido y para lecturas que dejan de llegar (se queda el último valor publicado); la marca de tiempo de la lectura, en UTC. |
| Variable de Logic | Los mismos miembros: el valor de la variable y `good` mientras es buena, `null` y `bad` en caso contrario; la marca de tiempo de su última escritura o de su último cálculo. |
| Alarma | `{"active": true, "acked": false, "state": 1, "priority": "High", "message": "Oven above 80 °C", "timestamp": "2026-10-04T12:30:15.250Z"}`: si está activa y reconocida, `state` como número (0 normal, 1 activa y no reconocida, 2 activa y reconocida, 3 normalizada y no reconocida), la prioridad por su nombre (Critical, High, Medium, Low), el mensaje (vacío si no hay) y la marca de tiempo de su última transición, como la sirve el [servidor OPC UA](connector-opcua-served-nodes#where-a-client-finds-each-source). |

Un valor viaja en su tipo JSON: un número, `true` o `false`, texto, una fecha y hora como texto
ISO 8601; un número que el JSON no lleva (NaN, un infinito) viaja como `null`. Un tag de un
dispositivo deshabilitado publica `null` y `bad`.

| Situación | Qué se publica |
| --- | --- |
| El valor o la calidad cambia | Esa fila, comprobada cinco veces por segundo. Un tag que se vuelve a leer con el mismo valor no se vuelve a publicar: la marca de tiempo sola no es un cambio. |
| La conexión se abre, o se vuelve a abrir | Todas las filas, con su estado presente. |
| El bróker está caído | Nada, y nada espera en una cola: cuando el bróker vuelve, cada fila se publica con su estado de ese momento. |
| El bróker rechaza un mensaje, o no lo confirma | Se vuelve a intentar a los 30 segundos. El diario técnico lo anota en el área Connector, componente `MQTT publication`, una vez y luego en un resumen cada pocos minutos mientras dure. |
| Se elimina una fuente | Nada más; el bróker conserva el último mensaje que guardó. |
| Se elimina una fila | Nada más; su mensaje retenido queda en el tópico hasta que se publique otra cosa allí. |

La estación lee lo que publica y no calcula nada para ello: la última lectura de un tag, el valor
de una variable, el estado de una alarma. No se suscribe a ningún tópico de una fila de solo lectura, así que un mensaje que otro sistema publica allí nunca llega a la estación; una fila en Lectura y escritura acepta valores en su tópico de escritura ([Escritura de vuelta](#writing-back)). Un agente lee las listas con
`mqtt_publish_list` y las cambia con `mqtt_publish_upsert` y `mqtt_publish_delete`
([Herramientas](agent-tools#connector--configure)):

```text
mqtt_publish_upsert(brokerId: "<id from mqtt_broker_list>", source: "Connector.Simulated.Oven.Temperature")
mqtt_publish_upsert(brokerId: "<id from mqtt_broker_list>", source: "Alarms.Oven.HighTemperature", topic: "plant/oven/alarms/high")
mqtt_publish_upsert(id: "<id from mqtt_publish_list>", access: "ReadAndWrite")
mqtt_publish_list()
```

## Publicación en Sparkplug B {#publishing-in-sparkplug-b}

Con **Formato de publicación** en **Sparkplug B**, el bróker publica la misma lista según la especificación
Sparkplug B, con la estación como nodo de borde: un host Sparkplug B, como el MQTT Engine de Ignition, lista
la estación bajo su grupo y su nodo de borde, cada dispositivo debajo y cada métrica con su tipo, sin ningún
mapeo hecho a mano. La conexión es la misma que usan los dispositivos del bróker, como en JSON.

| Nombre | Qué es |
| --- | --- |
| Grupo | **ID de grupo**, `GanterLab` mientras esté vacío. |
| Nodo de borde | El nombre de la estación, con cada `/`, `+` y `#` cambiado por `_` (los tres caracteres que un ID Sparkplug B no puede tener). |
| Dispositivo | Cada dispositivo del Connector con una fila de uno de sus tags, nombrado de la misma manera. |
| Métrica de un tag | El nombre del tag, en su dispositivo. |
| Métrica de una variable | `Logic/<folders>/<variable>`, en el nodo de borde. |
| Métricas de una alarma | Cinco, en el nodo de borde, bajo `Alarms/<folders>/<alarm>/`: `Active` y `Acked` (Boolean), `State` (un número: 0 normal, 1 activa y no reconocida, 2 activa y reconocida, 3 normalizada y no reconocida), `Priority` y `Message` (texto). |

Una `/` dentro de un nombre se convierte en `_`, así que las carpetas son los únicos niveles. Dos
dispositivos, o dos métricas, cuyos nombres quedan iguales reciben `_2`, `_3` en el orden de sus propios
nombres. Las filas conservan sus tópicos, en los que no se publica nada en Sparkplug B y en los que la lista
vuelve a publicar cuando el bróker vuelve a JSON.

| Mensaje | Cuándo |
| --- | --- |
| NDEATH, la muerte del nodo de borde | Se entrega al bróker como última voluntad de la conexión cada vez que se abre, para que el bróker lo publique si la estación se cae. La propia estación también lo publica antes de dejar de publicar: el bróker de vuelta en JSON, deshabilitado o eliminado, la lista vaciada, el grupo o el nombre de la estación cambiado, el runtime detenido. |
| NBIRTH, el nacimiento del nodo de borde | Cada vez que se abre la conexión, cuando un host pide un renacimiento (`Node Control/Rebirth`) y cuando una variable o una alarma entra, sale o cambia de nombre: cada métrica del nodo de borde con su valor actual, con `bdSeq` y `Node Control/Rebirth`. |
| DBIRTH, el nacimiento de un dispositivo | Después de cada NBIRTH, para cada dispositivo, y para un dispositivo que entra en la lista: cada tag del dispositivo con su valor actual. |
| NDATA, DDATA | Cinco veces por segundo, las métricas cuyo valor o calidad cambió: un mensaje para el nodo de borde, uno por dispositivo. |
| DDEATH, la muerte de un dispositivo | Cuando la última fila de un dispositivo sale de la lista, y antes de que un dispositivo cuyos tags cambiaron nazca de nuevo. |

Todo mensaje sale con QoS 0, nunca retenido, salvo el NDEATH. El número de secuencia `seq` empieza en 0 con
cada NBIRTH y cuenta cada mensaje posterior hasta 255, y luego vuelve a empezar; `bdSeq` cuenta las
conexiones, así que el NBIRTH y el NDEATH de una conexión llevan el mismo número. Nada espera en una cola
mientras el bróker está caído.

| Tipo de la fuente | Tipo Sparkplug B |
| --- | --- |
| `Boolean` | `Boolean` |
| `SByte`, `Byte` | `Int8`, `UInt8` |
| `Int16`, `UInt16` | `Int16`, `UInt16` |
| `Int32`, `UInt32` | `Int32`, `UInt32` |
| `Int64`, `UInt64` | `Int64`, `UInt64` |
| `Float`, `Double` | `Float`, `Double` |
| `String` | `String` |
| `DateTime` | `DateTime` |
| `Guid`, `ByteString` | `UUID`, `Bytes` |

Un tag se publica con su propio tipo; una variable con el tipo con el que la sirve el [servidor OPC UA](connector-opcua-served-nodes#where-a-client-finds-each-source) (un número entero como Int64, un número real como Double, un valor de presentación como texto). Cada métrica lleva su calidad en la propiedad `Quality`, la convención que leen los hosts Sparkplug B: 192 buena, 64 incierta (con el último valor publicado cuando la lectura dejó de llegar), 0 mala (la métrica queda entonces sin valor).

| Comando | Qué hace la estación |
| --- | --- |
| NCMD `Node Control/Rebirth` | Publica de nuevo el NBIRTH y todos los DBIRTH, con los valores actuales. |
| NCMD o DCMD con la métrica de una fila en Lectura y escritura | Escribe el valor en la fuente, como hace la [Escritura de vuelta](#writing-back): en el orden en que llegan los comandos, anotado en el diario técnico con el origen `MQTT <broker name>`. |
| Cualquier otra métrica, un comando sin valor o uno que el bróker entrega retenido | No se escribe nada; el diario técnico lo anota en el área Connector, componente `MQTT publication`. |

La estación escucha siempre los comandos para el nodo de borde, para que un host pueda pedir un
renacimiento, y los comandos para un dispositivo solo mientras una de sus filas está en Lectura y escritura.
Pasar un bróker a Sparkplug B hace que su conexión se abra de nuevo una vez, para que el bróker guarde la
última voluntad del nodo de borde; los dispositivos del bróker la atraviesan como cualquier reconexión. La
estación es un nodo de borde y no una aplicación host primaria: no espera el mensaje STATE de un host para
nacer.

```text
mqtt_broker_upsert(id: "<id from mqtt_broker_list>", publishFormat: "SparkplugB", sparkplugGroupId: "Plant1")
mqtt_publish_list()
```

## Escritura de vuelta {#writing-back}

Una fila de una [tarjeta Publicar](#the-publish-card) en **Lectura y escritura** acepta valores en
su tópico de escritura, el tópico de la fila seguido de `/set`: un panel en la nube que lee
`plant/oven/setpoint` envía una nueva consigna en `plant/oven/setpoint/set`. La estación la escribe
como un cliente de su [servidor Modbus](connector-modbus-server) o de su
[servidor OPC UA](connector-opcua-served-nodes) escribe una fila marcada de lectura y escritura.

| Situación | Qué ocurre |
| --- | --- |
| Una fila pasa a Lectura y escritura | La estación se suscribe a su tópico de escritura, con QoS 1, por la conexión que usan los dispositivos del bróker, y de nuevo cada vez que esa conexión se abre. Una fila de solo lectura no se suscribe, así que un mensaje en su tópico de escritura nunca llega a la estación. |
| Llega un mensaje al tópico de escritura | `{"value": 65}` o el valor solo (`65`, `true`, `Receta A`) se escribe en la fuente, en el orden en que llegan los mensajes: un tag por el mismo camino que la escritura de un operador, una variable de memoria tal cual. Un número JSON, `true` o `false` y un texto JSON llegan como lo que llevan; lo que no es JSON se lee como texto. El valor se convierte al tipo de la fuente como toda escritura, y un valor que el tipo no puede llevar se rechaza. |
| El mensaje no lleva valor | No se escribe nada: un mensaje vacío, `null`, un objeto JSON sin `value`, una lista. |
| El bróker entrega el mensaje que guardó | No se escribe nada. El bróker lo entrega cuando la estación se suscribe, y una consigna dejada allí hace una hora no es un valor que alguien envía ahora. Un valor publicado mientras la estación escucha se escribe, retenido o no. |
| La fuente ya no acepta escrituras | No se escribe nada: un tag que volvió a solo lectura, una variable eliminada. |
| La fila vuelve a Solo lectura, cambia de tópico o se elimina, o el bróker se deshabilita | La estación deja de escuchar ese tópico de escritura en un momento, y un mensaje que llega entretanto no escribe nada. |

El diario técnico anota cada escritura con el origen `MQTT <nombre del bróker>` en el área
Connector, como anota la escritura de un operador. Un mensaje que no se escribió queda anotado en
el área Connector, componente `MQTT publication`, una vez y luego en un resumen cada pocos minutos
mientras se repite.

El nombre de usuario y la contraseña del bróker son la única protección: quien el bróker deja
publicar en un tópico de escritura puede cambiar esa fuente, y en un bróker en la nube compartido
con otras personas eso es más gente que la planta. Dé a la cuenta del panel en el bróker solo los
tópicos que necesita. En un bróker en Sparkplug B, las mismas
filas aceptan comandos Sparkplug B (NCMD, DCMD) en lugar de un tópico de escritura ([Publicación en Sparkplug B](#publishing-in-sparkplug-b)).

```text
"C:\Program Files\mosquitto\mosquitto_pub" -h localhost -u dashboard -P <password> -t plant/oven/setpoint/set -m 65
```

## Seguridad {#security}

Con **Usar TLS**, el certificado del bróker se acepta de una de dos maneras: su cadena y su
nombre de host se validan contra el almacén de certificados de Windows, como en cualquier
conexión HTTPS de este ordenador, o usted aprobó ese mismo certificado en
[Acceso a los equipos](#equipment-access), en la página de este controlador. Un bróker cuyo
certificado procede de una autoridad de certificación propia pasa a ser de confianza en cuanto
esa autoridad se instala en el almacén de Windows, y uno con un certificado autofirmado no
necesita nada instalado en cuanto su certificado está aprobado. La revocación no se comprueba,
y nada acepta un certificado a ciegas: uno desconocido se rechaza, el bróker queda Inaccesible,
con el motivo, y sus dispositivos esperan la aprobación. La contraseña viaja dentro de la
conexión TLS; sin TLS viaja en claro, como avisa la tarjeta.

## Acceso a los equipos {#equipment-access}

Un bróker con un certificado autofirmado, o emitido por una autoridad propia, presenta un
certificado que ninguna raíz instalada en Windows avala. No hace falta instalar esa raíz en
este ordenador. Con **Usar TLS**, la primera conexión se rechaza, el bróker queda Inaccesible,
con el motivo, sus dispositivos dicen **Esperando la aprobación del certificado** en el árbol y
en sus páginas, cuyo botón **Revisar el certificado** abre esta tarjeta sobre el certificado, y
el certificado espera en **Acceso a los equipos**, al final de la página del controlador MQTT,
la misma tarjeta que lleva la página del controlador OPC UA, hasta que usted decida:

| Grupo | Qué muestra | Qué puede hacer |
|---|---|---|
| A la espera de aprobación | Cada certificado rechazado, con el nombre que declara: su huella digital SHA-256, la dirección del bróker en la que se encontró, cuándo lo intentó por última vez y cuántas veces, y sus detalles (sujeto, emisor, nombres de dominio, fechas de validez). El que abrió **Revisar el certificado** de un dispositivo aparece marcado como Seleccionado para revisión. | **Aprobar** acepta el certificado desde el siguiente intento de conexión de la sesión. **Bloquear** lo sigue rechazando sin volver a listarlo aquí. **Descartar todos los certificados pendientes** vacía la lista sin aprobar ni bloquear nada. |
| Equipos aprobados | Los certificados que usted aprobó o importó, con quién decidió y cuándo, y la dirección del bróker en la que se encontró cada uno por última vez. | **Retirar la aprobación** cierra la sesión del bróker y devuelve su certificado a la lista. **Importar certificado** aprueba un archivo de certificado público que ya tenga (.der, .cer, .crt o .pem) antes de la primera conexión. |
| Equipos bloqueados | Los certificados que usted bloqueó. Un certificado bloqueado se rechaza aunque Windows confíe en él. | **Desbloquear** devuelve el certificado a la lista, sin aprobarlo. |

Compruebe la huella digital con quien administra el bróker antes de aprobar el certificado. Una
aprobación acepta ese certificado y ningún otro, y solo dentro de sus fechas de validez;
sustituye a la raíz que falta en Windows y al nombre que lleva el certificado, así que un bróker
al que se llega por su dirección IP se conecta aunque su certificado nombre otro host, y nada
más del certificado se relaja. Cuando el bróker responde en una dirección cuyo certificado usted
aprobó con otro distinto, sus dispositivos dejan de leer, el nuevo espera aquí y la estación
avisa "Certificado cambiado: `<nombre>`" con un botón **Revisar**. Una exploración del bróker
desde la tarjeta Descubrir dispositivos se encuentra con las mismas decisiones. Un certificado
en el que Windows ya confía se acepta como antes y nunca aparece aquí; nada se aprueba
automáticamente.

La causa del bróker dice el motivo en lugar de un error de TLS sin nombre: "El certificado del
bróker en `<host>:<puerto>` (los primeros 16 caracteres de la huella digital) espera su
aprobación en Acceso a los equipos, en la página del controlador MQTT. La lectura empieza en
cuanto lo apruebe." Uno bloqueado dice "…está bloqueado en esta estación. Revíselo en Acceso a
los equipos, en la página del controlador MQTT." Un certificado fuera de sus fechas de validez,
revocado o con la firma rota se rechaza con ese motivo y nunca se lista, porque ninguna
aprobación lo vuelve válido.

Las decisiones se guardan en la carpeta de datos de la estación, en `ua\pki\mqtt`, y sobreviven
a un reinicio. Los verbos solo se dibujan para un rol que puede configurar el Connector, y solo
actúan en la ventana de la propia estación: un navegador en otro dispositivo lee la tarjeta sin
cambiarla, y ninguna herramienta de agente aprueba un certificado (`connector_browse` indica los
dispositivos del bróker con `awaitingCertificate`). La lista guarda hasta 100 certificados a la
espera de aprobación; cuando está llena, uno nuevo se sigue rechazando pero no se lista, y la
tarjeta lo dice.

## Preparar el bróker {#prepare-the-broker}

Un bróker tiene que aceptar conexiones del ordenador de la estación, y aceptar el inicio de
sesión de la estación. Eclipse Mosquitto 2, el bróker más común, no hace ninguna de las dos
cosas de fábrica: sin un listener en su mosquitto.conf solo escucha en su propio ordenador, y
cuando tiene uno rechaza los clientes sin nombre de usuario. MQTT no dice qué bróker respondió,
así que la estación nombra Mosquitto como el caso común, nunca como una certeza.

| El bróker responde | Qué dice la página del bróker | Qué lo cambia |
| --- | --- | --- |
| Su ordenador rechaza el puerto | El bróker solo acepta conexiones de su propio ordenador | En mosquitto.conf, la línea listener seguida del puerto (listener 1883), luego un reinicio del bróker; el puerto permitido en el firewall de su ordenador |
| Rechaza un cliente sin nombre de usuario | El bróker no acepta un cliente sin nombre de usuario | Nombre de usuario y Contraseña en la tarjeta Conexión con el bróker, con una cuenta del bróker; en Mosquitto 2 esa cuenta se crea con mosquitto_passwd y se indica con password_file en mosquitto.conf, o los clientes anónimos entran con allow_anonymous true |
| Rechaza el usuario o la contraseña | El rechazo del propio bróker, que señala Nombre de usuario y Contraseña en la tarjeta Conexión con el bróker | La cuenta, en el bróker o en la tarjeta |

Los dos primeros se dicen en la página del bróker, en lugar de su error en inglés, con estos
pasos y el puerto; sus dispositivos siguen señalando al bróker. El aviso desaparece solo en
cuanto la sesión se conecta. La marca del árbol y el registro conservan la causa en inglés,
que termina con los mismos pasos de Mosquitto.

## Instalar un bróker {#installing-a-broker}

La estación necesita un bróker al que conectarse, y el equipo también. Donde no lo tenga,
Eclipse Mosquitto es un bróker pequeño que se ejecuta como servicio de Windows:

1. Descargue el instalador para Windows de la página de descargas de mosquitto.org y
   ejecútelo. Instala el servicio **Mosquitto Broker** en `C:\Program Files\mosquitto`.
2. De serie, Mosquitto solo responde en el ordenador en el que se ejecuta. Para dejar entrar a
   los equipos de la red, añada un listener y un archivo de contraseñas al
   `mosquitto.conf` de esa carpeta:

   ```
   listener 1883
   password_file C:\Program Files\mosquitto\passwd
   ```

   Cree el archivo con la cuenta que usarán la estación y los equipos (el comando pide la
   contraseña):

   ```
   "C:\Program Files\mosquitto\mosquitto_passwd" -c "C:\Program Files\mosquitto\passwd" station
   ```

3. Reinicie el servicio desde un símbolo del sistema de administrador y permita la entrada
   del puerto TCP 1883 en el Firewall de Windows Defender para la red de los equipos:

   ```
   net stop mosquitto
   net start mosquitto
   ```

4. En la página del bróker, introduzca `localhost` como **Host o IP** cuando el bróker se
   ejecuta en el propio ordenador de la estación, o la dirección de ese ordenador en otro caso,
   **Puerto** 1883 y la cuenta que creó, y después **Habilitar**.

Para probar un tag simple antes de conectar los equipos, publique un valor de prueba
retenido desde la misma carpeta y apunte a él el **Tópico** de un tag:

```
"C:\Program Files\mosquitto\mosquitto_pub" -h localhost -u station -P <password> -t plant/line1/temperature -m 21.5 -r
```

## Límites que conviene conocer {#limits-worth-knowing}

- El controlador MQTT es un cliente, no un bróker: los equipos y la estación se conectan a un
  bróker, el [bróker MQTT](connector-mqtt-broker) propio de la estación u otro.
- La tarjeta Publicar de un bróker sale como JSON o como Sparkplug B, nunca las dos cosas a la
  vez. En Sparkplug B la estación es un nodo de borde, no una aplicación host primaria, y no usa
  alias, plantillas ni valores históricos.
- La estación no es una aplicación host de Sparkplug B: no publica ningún mensaje STATE. Un
  nodo de borde configurado para esperar a un host primario antes de publicar se queda
  callado.
- MQTT simple no dice nada de un publicador que se ha callado: un tag simple conserva el
  último valor que recibió mientras se mantiene la conexión con el bróker, fechado por su
  llegada, salvo que su dispositivo fije un **Tiempo máximo sin mensaje (s)**. Un tópico que solo se publica
  cuando cambia su valor no distingue un publicador callado de un valor estable, así que su
  dispositivo se queda en 0. Sparkplug B sí lo dice, con el certificado de muerte.
- La sesión empieza limpia: lo que se publicó mientras la estación no estaba conectada no se
  reenvía, y es el mensaje retenido de un tópico lo que devuelve un valor a su tag.
- Un tag lee un tópico, nunca un filtro, y un valor de él; una métrica Sparkplug B que es una
  matriz, un conjunto de datos, una plantilla o un conjunto de propiedades no se lee.
- Solo TCP, con o sin TLS: ni MQTT sobre WebSockets, ni certificado de cliente, ni brókeres
  anteriores a MQTT 3.1.1.
- Un dispositivo no se puede mover a otro bróker: créelo en el otro bróker y elimine el
  antiguo.
