Internet of Things/Protocollen/Websockets
Het Websockets protocol
Het websockets (ws) protocol is een webprotocol (naast HTTP), om bidirectionele communicatie tussen de client en de server mogelijk te maken, met een kleine overhead per bericht. In het bijzonder maakt dit protocol de "push" van berichten van de server naar de client (browser) mogelijk. (HTTP ondersteunt alleen de "pull" vanuit de client.)
Websockets worden gebruikt voor allerlei soorten webtoepassingen, vooral als er sprake is van "push" vanuit de server. Een voorbeeld is een chat-toepassing, waarbij de server de berichten distribueert naar de verschillende clients. Door de geringe overhead, en door de "push"-mogelijkheden, is het websockets protocol ook goed te gebruiken voor IoT-toepassingen.
Verbinding
Een websocket bestaat uit een bidirectionele client-server verbinding. Als de verbinding opgebouwd is kunnen de berichten met een minimale overhead overgestuurd worden.
Een binnenkomend bericht is een (asynchrone) event, die met behulp van een event-handler verwerkt kan worden.
De verbinding kan door allerlei oorzaken verbroken worden. In een webtoepassing met websockets moet dan regelmatig gecontroleerd worden of de verbinding nog bestaat, en moet deze eventueel opnieuw tot stand gebracht worden.
1-1 communicatie
De communicatie over een websocket is 1-1.
MQTT over websockets
Het MQTT-protocol kan ook op basis van websockets plaatsvinden, in plaats van over TCP. Dit maakt het mogelijk om vanuit de browser direct te communiceren met een MQTT-broker.
Samenvatting
verbinding tussen client en server |
bidirectioneel |
1-1 communicatie |
"push" vanuit server mogelijk |
Gebruik van websockets in de IoT-keten
We gebruiken het websockets-protocol voor het oversturen van sensor- en actuator-berichten tussen client (browser) en server, in beide richtingen. Voor deze berichten gebruiken we een JSON-representatie. Deze bestaat uit het JSON-bericht zoals dat via MQTT uitgewisseld wordt, uitgebreid met het MQTT-topic. Door deze extra informatie kan de (NodeRed) server deze berichten op een generieke manier afhandelen.
- Om het nog generieker te doen zouden we het "subscribe"-mechanisme aan de server kunnen toevoegen. Voorlopig hebben we dit opgelost door de webtoepassing op alle node-sensor-berichten te abonneren.
Websockets in NodeRed
NodeRed doet een "broadcast" doen over alle websockets, als de websocket output-node niet in één flow zit met een bijbehorende websocket input-node. Dit is een conventie binnen de NodeRed server; dit is geen eigenschap van het protocol zelf.
Voorbeeld
In het onderstaande voorbeeld wordt een websockets-verbinding opgezet, in de functie connect
.
connection = new WebSocket('wss://myserver.org/ws/sensorscontrol');
(Hier moet natuurlijk de juiste naam voor de server en de URL ingevuld worden.)
Bij het opzetten van deze verbinding worden verschillende functies gedefinieerd voor het afhandelen van events. De handler voor connection.onmessage
handelt een binnenkomend bericht van de server af; de inhoud hiervan is e.data
. In het voorbeeld wordt deze data alleen op het scherm getoond. In een uitgebreidere webtoepassing wordt deze data verder geïnterpreteerd.
connection.onmessage = function (e) {
var msg = document.getElementById("msgtext");
msg.value = msg.value + "\n" + e.data;
};
Door middel van de volgende constructie wordt elke minuut gecontroleerd of de verbinding nog bestaat. Zonodig wordt deze hersteld.
function checkConnection() {
if (connection !== null && connection.readyState == 3) {
connect();
}
}
connect();
setInterval(checkConnection, 60000);
Een "klik" op één van de buttons op het scherm resulteert in een JSON-bericht naar de server. Dit bericht is voorzien van een MQTT-topic: door de server wordt dit omgezet in een MQTT-bericht naar de broker, met het betreffende topic. Een voorbeeld van een dergelijk bericht: {"led0": 1, "topic": node/e1bd/actuators}
document.getElementById("onbutton").onclick = function () {
var nodeid = document.getElementById("nodeid").value;
var topic = 'node/' + nodeid + '/actuators';
connection.send('{"led0": 1, "topic": "' + topic + '"}');
};
Opmerking: vergelijk dit met het gebruik van HTTP voor het versturen van formulier-data.
Het complete programma (zoals gebruikt in een NodeRed template-node, als response op een html-request):
<!DOCTYPE html>
<html>
<head>
<title>Sensor data</title>
</head>
<body>
<h1>Buttons</h1>
<input type="button" id="onbutton">On</input>
<input type="button" id="offbutton">Off</input>
- Node:
<input type="textfield" id="nodeid" value="e1bd">
<h1>Sensor data</h1>
<textarea rows="10" cols="80" id="msgtext"></textarea>
<div id="messages"></div>
<script>
var connection = null;
function connect () {
console.log("(re)connecting...");
connection = new WebSocket('wss://myserver.org/ws/sensorscontrol');
// When the connection is open, send some data to the server
connection.onopen = function () {
connection.send('{"ping": 1, "topic": "ping"}');
};
// Log errors
connection.onerror = function (error) {
console.log('WebSocket Error ' + error);
};
// Log messages from the server
connection.onmessage = function (e) {
var msg = document.getElementById("msgtext");
msg.value = msg.value + "\n" + e.data;
};
}
function checkConnection() {
if (connection !== null && connection.readyState == 3) {
connect();
}
}
connect();
setInterval(checkConnection, 60000);
document.getElementById("onbutton").onclick = function () {
var nodeid = document.getElementById("nodeid").value;
var topic = 'node/' + nodeid + '/actuators';
connection.send('{"led0": 1, "topic": "' + topic + '"}');
};
document.getElementById("offbutton").onclick = function () {
var nodeid = document.getElementById("nodeid").value;
var topic = 'node/' + nodeid + '/actuators';
connection.send('{"led0": 0, "topic": "' + topic + '"}');
};
</script>
</body>
</html>