W&T conecta
Interfaces para TCP/IP, Ethernet, RS-232, RS-485, USB, 20mA, fibra óptica de vidrio y de plástico, http, SNMP, OPC, Modbus TCP, I/O digital, I/O analógico, ISA, PCI

Aplicación para Web-IO

Cliente web MQTT con JavaScript

como interfaz individual para Web-IO


MQTT Web-Client para Web-IO 4.0 Digital

En este tutorial se va a utilizar el cliente con JavaScript del proyecto Eclipse Paho para crear una interfaz propia basada en la web para Web-IO 4.0 Digital.

Para ello se guardan los estados de los Web-IO como Retained Message en el intermediario (broker) y Webclient los reproduce en una sencilla tabla en HTML.

Cuando se hace clic en una de las salidas, Web-Client envía una publicación en MQTT que ordena al Web-IO conmutar la salida correspondiente.


Estructura del cliente

Estructura de clientes web MQTT para Web-IO 4.0 Digital

Webclient pone a disposición una interfaz de usuario para el Web-IO remoto. La comunicación entre Web-IO y Web-Client tiene lugar a través del protocolo MQTT para el IdC.

El cliente está compuesto por los tres archivos:

  • webio.html
  • ui.js
  • mqtt.js
El archivo HTML proporciona el markup para la representación gráfica. El archivo de Javascript mqtt.js contiene las funciones específicas para MQTT, como la gestión de la conexión, la publicación (publish) y la suscripción (subscribe). El archivo ui.js es la interfaz entre el cliente MQTT y la superficie web. Contiene las funciones que actualizan la superficie cuando se recibe una suscripción y que activan el envío de un paquete PUBLISH para una interacción con el usuario.

Comunicación entre Web-Client y Web-IO

Desarrollo de la comunicación entre Web-IO y Web-Client

En el esquema contiguo sobre la comunicación entre Web-IO y Web-Client se ha prescindido del intermediario (broker) para hacerlo más sencillo.

La comunicación se ejecuta a través de los tópicos mywebio/<io>/status, con el que Web-IO publica el estado actual de conmutación, y mywebio/<io>/set, con el que la interfaz web envía una solicitud de conmutación al Web-IO.

  • 1 Una vez que se ha conectado el Web-Client, este recibe los estados de cada entrada y salida como publicaciones retenidas (Retained Messages) y los reproduce en una tabla en HTML.

  • 2 Cuando el usuario pulsa en una salida (aquí Output 0), Web-Client genera una publicación con la payload "ON" y el tópico "mywebio/output0/set". Cuando la solicitud está sin confirmar se muestra en Web-Client con un color menos intenso.

  • 3 Web-IO reacciona a la recepción de la suscripción activando la salida correspondiente. Luego envía una publicación con el nuevo estado como payload y el tópico "mywebio/output0/status". Cuando Web-Client recibe el mensaje cambia de nuevo la salida al color más intenso.

Aviso: Si se acumulan varias solicitudes seguidas sin confirmación por el Web-IO, la salida respectiva cambia a gris y solo se activa de nuevo cuando Web-IO publique su estado actual.


Preparación: configuración del intermediario y del Web-IO

Definir usuarios y derechos de acceso

Para este tutorial se utiliza, como en el tutorial de Box-2-Box vía MQTT, el intermediario gratuito cloudmqtt.com para pequeñas aplicaciones.

Para facilitar la comprensión se va a crear un único usuario con amplios derechos de acceso:

Nombre de usuario Contraseña Escritura Lectura
webio Super$icher123 # #

Configuración básica para MQTT

La configuración de Web-IO para trabajar con el intermediario se realiza en la página "Vías de comunicación >> MQTT".

Configuración básica para MQTT en Web-IO
  • 1 En primer lugar, active MQTT.

  • 2 Introduzca el nombre de usuario y la contraseña.

  • 3 Introduzca ahora los datos de conexión para el intermediario.

Crear acciones para conmutar las salidas

Abra ahora la página "Acciones" y pulse "Agregar" para crear las acciones para la recepción de una suscripción.

Acciones para recibir suscripciones en MQTT
  • 1 Active la acción e introduzca un nombre práctico.

  • 2 La señal de activación es la recepción de una suscripción en MQTT con la ruta de tópico mywebio/output0/set y el texto del tópico ON.

  • 3 Ante esta señal, el Web-IO tiene que conmutar la salida, en este caso Output 0 a "ON".

Repita el último paso hasta haber definido cuatro acciones: Output 0 en ON, Output 0 en OFF, Output 1 en ON y Output 1 en OFF.

Crear acciones para la publicación de los estados IO

En el último paso de la configuración se crean las acciones que generan una publicación en MQTT cuando cambia el estado en una entrada o salida cualquiera.

Acciones para recibir suscripciones en MQTT
  • 1 Cambie la acción a activo e introduzca un nombre.

  • 2 Seleccione como señal de activación Input y seleccione luego conmutar a OFF para Input0.

  • 3 Como acción se tiene que ejecutar una publicación MQTT.

  • 4 El tópico para la publicación de un nuevo estado es "mywebio/input0/status".

  • 5 La payload (texto del tópico) para conmutar a OFF es "OFF".

  • 6 El texto para Topic Clear es el mensaje que se envía cuando desaparece el trigger, es decir, "ON".

Repita la configuración de la acción para la otra entrada y para las dos salidas.


Implementación de Web-Client

A continuación trataremos en detalle los tres archivos webio.html, mqtt.js y ui.js. El archivo en HTML contiene únicamente los elementos básicos de diseño, por lo tanto solo nos ocuparemos de este brevemente. Le sigue una intensa deliberación de los dos archivos JavaScript.

Descargar todos los archivos

webio.html: La representación gráfica

<!doctype html>
<html lang="de">
	<head>
		<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js"></script>
		<script src="webio_mqtt.js" type="text/javascript"></script>
		<script src="ui.js" type="text/javascript" defer></script>
		<meta charset="utf-8">
		<title>My Web-IO</title>
		<style type="text/css">
			body {font-family: sans-serif;}
			table {empty-cells: show;}
			.on {background-color: #00FF00;}
			.off {background-color: #FF0000;}
			.set_on {background-color: #CCFFCC;}
			.set_off {background-color: #FFCCCC;}
			.unknown {background-color: #DDDDDD;}
			.on:before, .set_on:before {content: "ON";}
			.off:before, .set_off:before {content: "OFF";}
			.unknown:before	{content: "?";}
			#output0,#output1{cursor: pointer;}
		</style>
	</head>

	<body>
		<h1>My Web-IO</h1>
		<table>
		<tr>
			<th></th>
			<th>Input</th>
			<th>Output</th>
		</tr>
		<tr>
			<td >[0]</td>
			<td class="unknown" id="input0"></td>
			<td class="unknown" id="output0"></td>
		</tr>
		<tr>
			<td >[1]</td>
			<td class="unknown" id="input1"></td>
			<td class="unknown" id="output1"></td>
		</tr>
		</table>
	</body>
</html>
  • Líneas 4-6:
    Integrar el cliente con JavaScript en PAHO. URL alternativo: https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js
  • Líneas 7 y 8:
    Integrar los archivos en JS locales. El atributo defer se encarga de que solo se ejecuten las funciones que acceden a los elementos en HTML cuando se haya cargado la página completa.
  • Líneas 12-24:
    Información de estilos para los diferentes estados. Además de la asignación de un color se aplica al elemento bien el texto ON, OFF o ? delante.
  • Líneas 29-45:
    Tabla con las entradas y salidas. Las diferentes entradas y salidas están representadas aquí por celdas de la tabla a las que se ha asignado una clase CSS para la representación. Los elementos están identificados aquí por ID con el nombre idéntico a las entradas y salidas del Web-IO.

mqtt.js: El cliente MQTT propiamente dicho

El archivo mqtt.js contiene las funciones para establecer la conexión con el intermediario de MQTT y para recibir y enviar mensajes.

/*
	* MQTT-WebClient example for Web-IO 4.0
*/
var hostname = "m21.cloudmqtt.com";
var port = 37719;
var clientId = "webio4mqttexample";
clientId += new Date().getUTCMilliseconds();;
var username = "webclient";
var password = "Super$icher123";
var subscription = "mywebio/+/status";

mqttClient = new Paho.MQTT.Client(hostname, port, clientId);
mqttClient.onMessageArrived = MessageArrived;
mqttClient.onConnectionLost = ConnectionLost;
Connect();

/*Initiates a connection to the MQTT broker*/
function Connect(){
	mqttClient.connect({
	onSuccess: Connected,
	onFailure: ConnectionFailed,
	keepAliveInterval: 10,
	userName: username,
	useSSL: true,
	password: password});
}

/*Callback for successful MQTT connection */
function Connected() {
	console.log("Connected");
	mqttClient.subscribe(subscription);
}

/*Callback for failed connection*/
function ConnectionFailed(res) {
	console.log("Connect failed:" + res.errorMessage);
}

/*Callback for lost connection*/
function ConnectionLost(res) {
	if (res.errorCode !== 0) {
		console.log("Connection lost:" + res.errorMessage);
		Connect();
	}
}

/*Callback for incoming message processing */
function MessageArrived(message) {
	console.log(message.destinationName +" : " + message.payloadString);
	switch(message.payloadString){
		case "ON":
			displayClass = "on";
			break;
		case "OFF":
			displayClass = "off";
			break;
		default:
			displayClass = "unknown";
	}
	var topic = message.destinationName.split("/");
	if (topic.length == 3){
		var ioname = topic[1];
		UpdateElement(ioname, displayClass);
	}
}


Valores de configuración y establecimiento de la conexión

En primer lugar se aplican un par de valores de configuración:

var hostname 	= "m21.cloudmqtt.com";
var port 		= 37719;
var clientId	= "webio4mqttexample";
clientID += new Date().getUTCMilliseconds();
var username	= "webclient";
var password	= "Super$icher123";
var subscription = "mywebio/+/status";

  • Líneas 4 y 5:
    Datos de conexión para el intermediario de MQTT.
  • Línea 6:
    String exclusivo que identifica el cliente a acceder.
  • Línea 7:
    La instrucción new Date().getUTCMilliseconds() se encarga de que se puedan abrir simultáneamente varias instancias del cliente insertando un sello de tiempo actual en el ID del cliente. Varias conexiones con un mismo ID de cliente interrumpiría al intermediario según el estándar.
  • Líneas 8 y 9:
    Datos de login del usuario
  • Línea 10:
    Tópico con el que Web-IO publica sus estados.

Le sigue la inicialización del cliente:

mqttClient = new Paho.MQTT.Client(hostname, port, clientId);
mqttClient.onMessageArrived = MessageArrived;
mqttClient.onConnectionLost = ConnectionLost;
Connect();
  • Línea 12:
    Inicializacion del cliente MQTT con el nombre de host del intermediario, el puerto y el ID unívoco del cliente.
  • Línea 13:
    MessageArrived() Función Callback que se activa al recibo de una suscripción.
  • Línea 14:
    ConnectionLost() Se activa en caso de corte de la conexión.
  • Línea 15:
    Con Connect se establece la conexión.

La función Connect() es responsable del establecimiento de la conexión con el intermediario de MQTT. El objeto de la configuración contiene, además de los parámetros ya indicados, las funciones de Callback para un establecimiento de la conexión correcto o fallido.

/*Initiates a connection to the MQTT broker*/
function Connect(){
	mqttclient.connect({
		onSuccess:	Connected,
		onFailure:	ConnectionFailed,
		keepAliveInterval: 10,
		userName:	username,
		useSSL:		true,
		password:	password
	});
}
  • Línea 19:
    La conexión abre el método connect() del cliente MQTT.
  • Línea 20:
    Una vez establecida la conexión con éxito se ejecuta la función Connected() Callback.
  • Línea 21:
    Si falla el intento de establecer la conexión se activa ConnectionFailed().
  • Línea 24:
    La comunicación se realiza codificada por TLS.


Las funciones Callback

En la primera parte se han mencionado cuatro funciones Callback: MessageArrived() y ConnectionLost() han sido aplicadas como atributos del cliente MQTT; Connected() y ConnectionFailed() han sido otorgadas a la función Connect() en el objeto de configuración.

Connected() se ejecuta cuando se ha establecido correctamente la conexión.

/*Callback for successful MQTT connection */
function Connected() {
	console.log("Connected");
	mqttclient.subscribe(subscription);
}				
  • Línea 31:
    El estado de la conexión se emite a través de la consola de JavaScript.
  • Línea 32:
    Al no tratarse de una conexión persistente, es necesario abonar el tópico para mensajes de Web-IO cada vez que se establece la conexión.

Cuando falla la conexión, ConnectionFailed() ofrece información en la consola de JavaScript.

/*Callback for failed connection*/
function ConnectionFailed(res) {
		console.log("Connect failed:" + res.errorMessage);
}

En caso de corte en la conexión se abre ConnectionLost().

/*Callback for lost connection*/
function ConnectionLost(res) {
	if (res.errorCode !== 0)
	{
	console.log("Connection lost:" + res.errorMessage);
	Connect();
	}
}
  • Línea 43:
    A través de la consola de JavaScript se emite la información acerca del corte de la conexión.
  • Línea 44:
    Luego se establece una nueva conexión.

La función más completa es MessageArrived(). La parte superior de la función analiza el string del PayLoad. Con este se determina la clase CSS a asignar al elemento respectivo.

/*Callback for incoming message processing */
function MessageArrived(message) {
	switch(message.payloadString){
		case "ON":
			displayClass = "on";
			break;
		case "OFF":
			displayClass = "off";
			break;
		default:
			displayClass = "unknown";
	}
  • Línea 50:
    El objeto mensaje posee un atributo payloadString, que incluye el contenido del mensaje transmitido.
  • Líneas 51-56:
    Cuando el contenido del mensaje es "ON", se cambia la variable displayClass a "on". Con el PayLoad "OFF" a "off".
  • Líneas 57-58:
    Con una payload defectuosa se aplica la clase de display "unknown".

La parte inferior de la función determina la entrada o salida que debe ser actualizada:

var topic = message.destinationName.split("/");
	if (topic.length == 3){
		var ioname = topic[1];
		UpdateElement(ioname, displayClass);
	}
}
  • Línea 61:
    En primer lugar se descompone message.destinationName, es decir el tópico del mensaje por el carácter «/» en sus distintas partes con el método de string split() y se guardan en Array topic.
  • Línea 63:
    En mywebio/ioname/status, el elemento central es el nombre de la entrada o salida respectivas.
  • Línea 64:
    El elemento DOM ioname recibe en la función UpdateElement() la clase CSS displayClass.

ui.js: La conexión entre la página HTML y el cliente MQTT

El ui.js contiene las funciones que actualizan la presentación de la página HTML y que activan las funciones del cliente MQTT tras una intervención del usuario.

/*
	* Web-IO 4.0: MQTT WebSocket example
	*/

/* Updates the CSS class of an DOM element */
function UpdateElement(ioname, displayClass){
	var cell = document.getElementById(ioname);
	if (cell){
		cell.className = displayClass;
	}
	}

	/* Toggles an input in the web interfaces and
	* initiates an MQTT publish */
function ToggleOutput(ioname){
	var cell = document.getElementById(ioname);
	switch (cell.className){
	case "on":
		var message = new Paho.MQTT.Message("OFF");
		message.destinationName = "webio/" + ioname + "/set";
		mqttClient.send(message);
		cell.className = "set_off";
		break;
	case "off":
		var message = new Paho.MQTT.Message("ON");
		message.destinationName = "webio/" + ioname + "/set";
		mqttClient.send(message);
		cell.className = "set_on";
		break;
	default:
		cell.className = "unknown";
		break;
	}

};

/* Adds an Click-Event-Listener to a table cell, so that after
	* a click the element can is toggeled */
function EnableToggle(ioname){
		var cell = document.getElementById(ioname)
		if (cell){
			cell.addEventListener("click",
				function(){
				ToggleOutput(ioname)
			},
				true);
		}
	}

/*Initialize elements that can be toggled my by click*/
EnableToggle("output0");
EnableToggle("output1");


Actualizar la pantalla a través del cliente MQTT

Con la función UpdateElement() el cliente MQTT cambia la clase CSS de la celda respectiva de la tabla al estado actual cuando se recibe una suscripción.

/* Updates the CSS class of an DOM element */
function UpdateElement(ioname, displayClass){
	var cell = document.getElementById(ioname);
	if (cell){
		cell.className = displayClass;
	}
	}
  • Línea 6:
    La función recibe el ID del elemento DOM y la clase CSS.
  • Línea 9:
    La clase se asigna al elemento.

Interacciones de los usuarios

La función ToggleOutput() adapta la presentación cuando se hace clic en una salida y envía la correspondiente publicación MQTT. Si la clase presente es on, se le asigna la clase set_off si la clase es off, se le asigna la nueva clase set_on. En todos los demás casos, el estado actual de la salida está sin confirmar y a la indicación se asigna la clase unknown.

/* Toggles an input in the web interfaces and
	* initiates an MQTT publish
	*/
function ToggleOutput(ioname){
	var cell = document.getElementById(ioname);
	switch (cell.className){
	case "on":
		var message = new Paho.MQTT.Message("OFF");
		message.destinationName = "webio/" + ioname + "/set";
		mqttClient.send(message);
		cell.className = "set_off";
		break;
	case "off":
		var message = new Paho.MQTT.Message("ON");
		message.destinationName = "webio/" + ioname + "/set";
		mqttClient.send(message);
		cell.className = "set_on";
		break;
	default:
		cell.className = "unknown";
		break;
	}
};
  • Línea 16:
    La función recibe el ID del elemento DOM que representa la salida.
  • Línea 18:
    Independiente de la clase presente del elemento se aplica
  • Líneas 19-24, 25-30 y 21-33:
    la nueva clase a set_on, set_off o unknown.

Para que se abra la función ToggleOutput() al hacer clic en una celda de la tabla, EnableToggle() añade a los elementos transmitidos un manejador de eventos (event handler) para un clic del ratón.

/* Adds an Click-Event-Listener to a table cell, so that after
	* a click the element can is toggeled
	*/
function EnableToggle(ioname){
		var cell = document.getElementById(ioname)
		if (cell){
			cell.addEventListener("click",
				function(){
				ToggleOutput(ioname)
			},
				true);
		}
	}
  • Línea 40:
    El método recibe el nombre de un IO transferido.
  • Línea 41:
    Este nombre se utiliza para referenciar la celda respectiva de la tabla.
  • Líneas 43-47:
    Con el método addEventListener() se asigna al elemento cell un manejador de eventos para un click. Cuando se hace clic sobre el elemento respectivo, se debe abrir la función ToggleOutput() con el nombre del IO como parámetro.

A continuación se abre EnableToggle() para los dos elementos "output1" y "output2":

/*Initialize elements that can be toggled my by click*/
EnableToggle("output0");
EnableToggle("output1");

Practicar este tutorial con hardware

¿Desea practicar este tutorial pero le falta el hardware necesario? Con agrado pondremos un Web-IO Digital 4.0 como muestra a su disposición.

¿Alguna otra pregunta sobre Web-IO Digital o MQTT?

El Sr. Thiel le atenderá con mucho gusto.
Teléfono: +49 202/2680-110 (lu-vi de 8-17 horas)
E-mail: f.thiel@wut.de