Bijeenkomst 3#
Inleiding#
In deze bijeenkomst gaan we aan de slag met NodeRed, om sensoren en actuatoren te koppelen, bijvoorbeeld in de vorm van een (eenvoudig) besturingsalgoritme.
We gaan in op de extra mogelijkheden die NodeRed biedt (voor een vervolg…), bijvoorbeeld om data en diensten in het internet te koppelen aan IoT-sensoren en actuatoren.
Sturen met NodeRed#
In het NodeRed-dashboard kun je de waarden van de sensoren weergeven, en het verloop van de waarden volgen. Via de knoppen in het dashboard kun je de actuatoren aansturen. Het dashboard is daarmee vooral een gebruikersinterface van het IoT.
Met NodeRed kun je nog (veel) meer: je kunt sensoren en actuatoren koppelen via een besturingsalgoritme. Enkele eenvoudige voorbeelden:
gebruik de knoppen A en B om een lamp (LED, microbit-display) aan- en uit te zetten.
verfijning: gebruik deze knoppen om een lamp in verschillende lichtsterktes aan- en uit te zetten (“dimmer”).
verfijning: gebruik de knoppen van verschillende microbits om een lamp aan- en uit te zetten: “hotelschakelaar”.
gebruik een “presence” sensor, bijvoorbeeld een PIR-sensor, om een lamp aan te zetten gedurende een bepaalde periode.
(je vindt een dergelijke automatische lamp op veel plekken, zowel bij buitenverlichting als bij binnenverlichting)
gebruik een lichtsensor om een lamp automatisch aan te zetten als het donker wordt; en, omgekeerd, uit te schakelen als het licht is.
Idempotente interfaces#
We gebruiken twee verschillende knoppen voor het aan- en uitzetten van de lamp: met knop A zet je deze aan, met knop B zet je deze uit.
Dit is een voorbeeld van een idempotent interface. Daarbij maakt het niet uit of je een opdracht éénmaal of meerdere malen achter elkaar uitvoert: A; B
is hetzelfde als A; A; A; B
of A; B; B; B
.
Zo’n interface is handig als de communicatie of de uitvoering niet helemaal betrouwbaar is, of soms erg traag: als het niet lijkt te werken, probeer je het nog een keer.
Als je A indrukt, en de lamp gaat niet binnen korte tijd aan, dan druk je A nog een keer in.
Als je dezelfde schakelaar zou gebruiken voor het aan- en uitzetten, wat in principe best kan, dan geeft bovenstaande herhaling juist het tegenovergestelde effect van wat je probeert te bereiken: als je het nog een keer probeert, gaat de lamp uit.
De HTTP GET-opdracht is ook idempotent: als een pagina niet snel genoeg laadt in de browser, dan probeer je het nog een keer (“refresh” van de pagina). Bij een POST-opdracht kan dat niet: dan vraag de browser of je het formulier nog een keer wilt opsturen.
Aan- en uitschakelen van een lamp (LED, display)#
We gebruiken de onderstaande flow voor het aan- en uitzetten van een lamp:
De switch node gebruiken we om alleen de “=1” berichten door te laten.
De instellingen voor deze node zie je hieronder.
flow-code on-off-flow
[
{
"id": "1e58540bf9586a18",
"type": "tab",
"label": "On-off-flow",
"disabled": false,
"info": "",
"env": []
},
{
"id": "320f13370293c2db",
"type": "mqtt in",
"z": "1e58540bf9586a18",
"name": "",
"topic": "node/a5cf/sensors",
"qos": "2",
"datatype": "json",
"broker": "35520d39dbc586cb",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 130,
"y": 260,
"wires": [
[
"b4f93f5e370544e4",
"f01490c58560094a"
]
]
},
{
"id": "b4f93f5e370544e4",
"type": "function",
"z": "1e58540bf9586a18",
"name": "2-button-A",
"func": "if (msg.payload.payload[2]) {\n msg.payload = msg.payload.payload[2].dIn;\n return msg;\n} else {\n return null;\n}\n",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 170,
"y": 340,
"wires": [
[
"772510ec19db85ea"
]
]
},
{
"id": "3805ee2aa11b1722",
"type": "template",
"z": "1e58540bf9586a18",
"name": "0-dOut-on",
"field": "payload",
"fieldType": "msg",
"format": "json",
"syntax": "mustache",
"template": "{\"0\": {\"dOut\": 1}}",
"output": "str",
"x": 490,
"y": 340,
"wires": [
[
"5d9ac27f7196e3e7"
]
]
},
{
"id": "6b7b32b941377721",
"type": "template",
"z": "1e58540bf9586a18",
"name": "0-dOut-off",
"field": "payload",
"fieldType": "msg",
"format": "json",
"syntax": "mustache",
"template": "{\"0\": {\"dOut\": 0}}",
"output": "str",
"x": 490,
"y": 420,
"wires": [
[
"5d9ac27f7196e3e7"
]
]
},
{
"id": "5d9ac27f7196e3e7",
"type": "mqtt out",
"z": "1e58540bf9586a18",
"name": "",
"topic": "node/a5cf/actuators",
"qos": "",
"retain": "",
"respTopic": "",
"contentType": "",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "35520d39dbc586cb",
"x": 740,
"y": 380,
"wires": []
},
{
"id": "772510ec19db85ea",
"type": "switch",
"z": "1e58540bf9586a18",
"name": "=1?",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "1",
"vt": "num"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 330,
"y": 340,
"wires": [
[
"3805ee2aa11b1722"
]
]
},
{
"id": "f01490c58560094a",
"type": "function",
"z": "1e58540bf9586a18",
"name": "3-button-B",
"func": "if (msg.payload.payload[3]) {\n msg.payload = msg.payload.payload[3].dIn;\n return msg;\n} else {\n return null;\n}",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 170,
"y": 420,
"wires": [
[
"66a8b939f86a3ec0"
]
]
},
{
"id": "66a8b939f86a3ec0",
"type": "switch",
"z": "1e58540bf9586a18",
"name": "=1?",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "1",
"vt": "num"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 330,
"y": 420,
"wires": [
[
"6b7b32b941377721"
]
]
},
{
"id": "35520d39dbc586cb",
"type": "mqtt-broker",
"name": "",
"broker": "infvopedia.nl",
"port": "1883",
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": "4",
"keepalive": "60",
"cleansession": true,
"birthTopic": "",
"birthQos": "0",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
}
]
Opdracht stuur met de knoppen van de ene microbit, het display van een andere microbit aan.
Opdracht maak een hotelschakelaar: met de knoppen van twee microbits kun je, onafhankelijk van elkaar, het display van een andere microbit aansturen.
Tijdschakelaar#
We willen een besturing maken voor een lamp (LED, display van een microbit) die de lamp aanzet als er “presence” gesignaleerd wordt, en die dan 10 seconden blijft branden. Als er in die 10 seconden opnieuw “presence” gedetecteerd wordt, dan gaat er een nieuwe periode van 10 seconden in.
Een dergelijke schakeling vind je bijvoorbeeld bij veel buitenlampen. Als “presence” sensor wordt dan meestal een PIR (passive infrared) sensor gebruikt, waarmee de beweging van mensen (of andere grote IR-stralers) gedetecteerd wordt. De periode is dan meestal wat langer dan 10 seconden.
De NodeRed-node die we hiervoor gebruiken is de Trigger: deze stuurt het ontvangen bericht door, en stuurt een bepaalde periode een volgend bericht. Met dit tweede bericht kunnen we de lamp weer uitschakelen.
flow-code trigger-flow
[
{
"id": "ad100c530b76dafb",
"type": "tab",
"label": "Trigger-flow",
"disabled": false,
"info": "",
"env": []
},
{
"id": "8d4fc8d2973e5049",
"type": "trigger",
"z": "ad100c530b76dafb",
"name": "",
"op1": "1",
"op2": "0",
"op1type": "str",
"op2type": "str",
"duration": "10",
"extend": false,
"overrideDelay": false,
"units": "s",
"reset": "",
"bytopic": "all",
"topic": "topic",
"outputs": 2,
"x": 210,
"y": 340,
"wires": [
[
"f906a827b3cad1c7"
],
[
"f236688d2dd08df0"
]
]
},
{
"id": "c01731b617d69c9c",
"type": "mqtt in",
"z": "ad100c530b76dafb",
"name": "",
"topic": "node/a5cf/sensors",
"qos": "2",
"datatype": "json",
"broker": "35520d39dbc586cb",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 150,
"y": 200,
"wires": [
[
"5c6cccb0d0a6e9de"
]
]
},
{
"id": "5c6cccb0d0a6e9de",
"type": "function",
"z": "ad100c530b76dafb",
"name": "2-button-A",
"func": "if (msg.payload.payload[2]) {\n msg.payload = msg.payload.payload[2].dIn;\n return msg;\n} else {\n return null;\n}\n",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 390,
"y": 200,
"wires": [
[
"3b4f116ee1bbf773"
]
]
},
{
"id": "f906a827b3cad1c7",
"type": "template",
"z": "ad100c530b76dafb",
"name": "0-dOut-on",
"field": "payload",
"fieldType": "msg",
"format": "json",
"syntax": "mustache",
"template": "{\"0\": {\"dOut\": 1}}",
"output": "str",
"x": 450,
"y": 320,
"wires": [
[
"b38feded425cc79b"
]
]
},
{
"id": "f236688d2dd08df0",
"type": "template",
"z": "ad100c530b76dafb",
"name": "0-dOut-off",
"field": "payload",
"fieldType": "msg",
"format": "json",
"syntax": "mustache",
"template": "{\"0\": {\"dOut\": 0}}",
"output": "str",
"x": 450,
"y": 360,
"wires": [
[
"b38feded425cc79b"
]
]
},
{
"id": "b38feded425cc79b",
"type": "mqtt out",
"z": "ad100c530b76dafb",
"name": "",
"topic": "node/a5cf/actuators",
"qos": "",
"retain": "",
"respTopic": "",
"contentType": "",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "35520d39dbc586cb",
"x": 700,
"y": 340,
"wires": []
},
{
"id": "3b4f116ee1bbf773",
"type": "switch",
"z": "ad100c530b76dafb",
"name": "=1?",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "1",
"vt": "num"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 570,
"y": 200,
"wires": [
[
"8d4fc8d2973e5049"
]
]
},
{
"id": "35520d39dbc586cb",
"type": "mqtt-broker",
"name": "",
"broker": "infvopedia.nl",
"port": "1883",
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": "4",
"keepalive": "60",
"cleansession": true,
"birthTopic": "",
"birthQos": "0",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
}
]
Schakelen op basis van signalen#
In de onderstaande flow schakelen we de lamp (display) aan als het lichtniveau onder een bepaalde waarde ligt, en uit als het boven een (wat hogere) waarde ligt. We gebruiken met opzet een wat hogere waarde voor het uitzetten, om te voorkomen dat de lamp rond het schakelpunt blijft knipperen. Met de hysterese proberen we dit te voorkomen.
Deze flow heeft echter nog een (serieus) probleem: omdat de IoT-knopen bij het ontvangen van een actuator-bericht ook weer de sensorwaarden opsturen, als vorm van acknowledgement, ontstaat er een lus waarin steeds weer sensorberichten en actuatorberichten verstuurd worden. (Een actuator-bericht geeft een sensor-bericht geeft een actuator-bericht enz.) We voorkomen dit door een filter in de lus te plaatsen, in dit geval aan de kant van de output: als het vorige actuator-bericht gelijk is aan het vorige, sturen we dit niet door. (Dit kun je zien als een “idempotentie-filter”).
(Je kunt het filter eventueel ook aan de kant van de input van de switch plaatsen: als het lichtniveau niet (of nauwelijks) verandert, stuur je het sensorbericht niet door. Je moet in dat geval wel rekening houden met kleine betekenisloze veranderingen die je niet wilt doorsturen. Aan de kant van de output gaat het om een binair signaal: dat laat zich gemakkelijker filteren.)
flow-code lightlevel-switch-filter-flow
[
{
"id": "50934e14c2e5d6dc",
"type": "tab",
"label": "Flow 13",
"disabled": false,
"info": "",
"env": []
},
{
"id": "15f9249a4f42894a",
"type": "mqtt in",
"z": "50934e14c2e5d6dc",
"name": "",
"topic": "node/a5cf/sensors",
"qos": "2",
"datatype": "json",
"broker": "35520d39dbc586cb",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 110,
"y": 160,
"wires": [
[
"de740d6dc1d3456c"
]
]
},
{
"id": "f681d0f55c09a00c",
"type": "template",
"z": "50934e14c2e5d6dc",
"name": "0-dOut-on",
"field": "payload",
"fieldType": "msg",
"format": "json",
"syntax": "mustache",
"template": "{\"0\": {\"dOut\": 1}}",
"output": "str",
"x": 470,
"y": 240,
"wires": [
[
"11d88252c67fdc68"
]
]
},
{
"id": "2ba2dcf826fbee0c",
"type": "template",
"z": "50934e14c2e5d6dc",
"name": "0-dOut-off",
"field": "payload",
"fieldType": "msg",
"format": "json",
"syntax": "mustache",
"template": "{\"0\": {\"dOut\": 0}}",
"output": "str",
"x": 470,
"y": 320,
"wires": [
[
"11d88252c67fdc68"
]
]
},
{
"id": "09ddf80170f2a1fe",
"type": "mqtt out",
"z": "50934e14c2e5d6dc",
"name": "",
"topic": "node/a5cf/actuators",
"qos": "",
"retain": "",
"respTopic": "",
"contentType": "",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "35520d39dbc586cb",
"x": 680,
"y": 400,
"wires": []
},
{
"id": "7b9ee91e26e352b5",
"type": "switch",
"z": "50934e14c2e5d6dc",
"name": "",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "lte",
"v": "100",
"vt": "num"
},
{
"t": "gt",
"v": "110",
"vt": "num"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 290,
"y": 280,
"wires": [
[
"f681d0f55c09a00c"
],
[
"2ba2dcf826fbee0c"
]
]
},
{
"id": "de740d6dc1d3456c",
"type": "function",
"z": "50934e14c2e5d6dc",
"name": "8-lightlevel",
"func": "if (msg.payload.payload[8]) {\n msg.payload = msg.payload.payload[8].aIn;\n return msg;\n} else {\n return null;\n}",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 130,
"y": 280,
"wires": [
[
"7b9ee91e26e352b5"
]
]
},
{
"id": "11d88252c67fdc68",
"type": "rbe",
"z": "50934e14c2e5d6dc",
"name": "",
"func": "rbe",
"gap": "",
"start": "",
"inout": "out",
"septopics": true,
"property": "payload",
"topi": "topic",
"x": 670,
"y": 280,
"wires": [
[
"09ddf80170f2a1fe"
]
]
},
{
"id": "35520d39dbc586cb",
"type": "mqtt-broker",
"name": "",
"broker": "infvopedia.nl",
"port": "1883",
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": "4",
"keepalive": "60",
"cleansession": true,
"birthTopic": "",
"birthQos": "0",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
}
]
Combineren van events en signalen#
We hebben eerder gezien hoe je op basis van een “event”, zoals een bewegingssensor, een lamp kunt aansturen. Maar vaak wil je zoiets combineren met een sensorsignaal. In dit voorbeeld willen we dat de lamp alleen aangaat als het lichtniveau onder een bepaalde grens ligt. Een aanname hierbij is dat het lichtniveau niet snel verandert. (In dit voorbeeld kan de sensorsignaal van het lichtniveau ook van een andere IoT-knoop afkomstig zijn.)
In NodeRed kun je waarden bewaren in globale variabelen, die je in nodes kunt zetten en raadplegen. We bewaren de waarde van het lichtniveau-signaal in een zogenaamde flow-variabele. Bij het afhandelen van de bewegings-event gebruiken we die variabele om te bepalen of de lamp aangezet moet worden.
De functie voor het opslaan van het lichtniveau ziet er als volgt uit:
flow.set("lightlevel", msg.payload);
return msg;
flow-code on-off-switch-trigger-flow
[
{
"id": "a979da44d1e91915",
"type": "tab",
"label": "nodered-on-off-switch-trigger",
"disabled": false,
"info": "",
"env": []
},
{
"id": "28014ac2c7e7993a",
"type": "trigger",
"z": "a979da44d1e91915",
"name": "",
"op1": "1",
"op2": "0",
"op1type": "str",
"op2type": "str",
"duration": "10",
"extend": false,
"overrideDelay": false,
"units": "s",
"reset": "",
"bytopic": "all",
"topic": "topic",
"outputs": 2,
"x": 210,
"y": 300,
"wires": [
[
"cdc1815799fbc3eb"
],
[
"ce0e57c014248b5a"
]
]
},
{
"id": "20180e69a6b3375d",
"type": "mqtt in",
"z": "a979da44d1e91915",
"name": "",
"topic": "node/a5cf/sensors",
"qos": "2",
"datatype": "json",
"broker": "35520d39dbc586cb",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 150,
"y": 160,
"wires": [
[
"0825cb5c3a0c0b33"
]
]
},
{
"id": "0825cb5c3a0c0b33",
"type": "function",
"z": "a979da44d1e91915",
"name": "2-button-A",
"func": "if (msg.payload.payload[2]) {\n msg.payload = msg.payload.payload[2].dIn;\n return msg;\n} else {\n return null;\n}\n",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 350,
"y": 160,
"wires": [
[
"76c12a6a83a767a9"
]
]
},
{
"id": "cdc1815799fbc3eb",
"type": "template",
"z": "a979da44d1e91915",
"name": "0-dOut-on",
"field": "payload",
"fieldType": "msg",
"format": "json",
"syntax": "mustache",
"template": "{\"0\": {\"dOut\": 1}}",
"output": "str",
"x": 450,
"y": 280,
"wires": [
[
"fb8361a4f966ec82"
]
]
},
{
"id": "ce0e57c014248b5a",
"type": "template",
"z": "a979da44d1e91915",
"name": "0-dOut-off",
"field": "payload",
"fieldType": "msg",
"format": "json",
"syntax": "mustache",
"template": "{\"0\": {\"dOut\": 0}}",
"output": "str",
"x": 450,
"y": 320,
"wires": [
[
"fb8361a4f966ec82"
]
]
},
{
"id": "fb8361a4f966ec82",
"type": "mqtt out",
"z": "a979da44d1e91915",
"name": "",
"topic": "node/a5cf/actuators",
"qos": "",
"retain": "",
"respTopic": "",
"contentType": "",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "35520d39dbc586cb",
"x": 700,
"y": 300,
"wires": []
},
{
"id": "3398e68f40f8eec3",
"type": "mqtt in",
"z": "a979da44d1e91915",
"name": "",
"topic": "node/a5cf/sensors",
"qos": "2",
"datatype": "json",
"broker": "35520d39dbc586cb",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 150,
"y": 80,
"wires": [
[
"ae1c50be64315f70"
]
]
},
{
"id": "ae1c50be64315f70",
"type": "function",
"z": "a979da44d1e91915",
"name": "8-lightlevel",
"func": "if (msg.payload.payload[8]) {\n msg.payload = msg.payload.payload[8].aIn;\n return msg;\n} else {\n return null;\n}",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 350,
"y": 80,
"wires": [
[
"c9dddfba615173f9"
]
]
},
{
"id": "c9dddfba615173f9",
"type": "function",
"z": "a979da44d1e91915",
"name": "Store light level",
"func": "flow.set(\"lightlevel\", msg.payload);\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 540,
"y": 80,
"wires": [
[]
]
},
{
"id": "76c12a6a83a767a9",
"type": "switch",
"z": "a979da44d1e91915",
"name": "=1?",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "1",
"vt": "num"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 510,
"y": 160,
"wires": [
[
"7d999de5faa99fc8"
]
]
},
{
"id": "7d999de5faa99fc8",
"type": "switch",
"z": "a979da44d1e91915",
"name": "lightlevel low?",
"property": "lightlevel",
"propertyType": "flow",
"rules": [
{
"t": "lt",
"v": "150",
"vt": "num"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 680,
"y": 160,
"wires": [
[
"28014ac2c7e7993a"
]
]
},
{
"id": "35520d39dbc586cb",
"type": "mqtt-broker",
"name": "",
"broker": "infvopedia.nl",
"port": "1883",
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": "4",
"keepalive": "60",
"cleansession": true,
"birthTopic": "",
"birthQos": "0",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
}
]
Gebruik van externe diensten#
Als voorbeeld van een externe dienst laten we zien hoe je het actuele weerbericht voor een plaats kunt opvragen via http://openweathermap.org.
Voor het gebruik van de API van deze website heb je een API-key nodig. Deze kun je aanmaken als je een account hebt. Voor eenvoudig gebruik is zo’n account gratis.
In de voorbeeld-flow vragen we met een HTTP GET-request het actuele weerbericht op. We gebruiken daarvan alleen de gegevens over de tijd van zonsopgang, als timestamp (We hebben een extra flow om deze te vergelijken met timestamp van de Inject-node, om te weten of deze beide dezelfde basis gebruiken, nl. UTC.)
De URL in het GET request moet de volgende gegevens bevatten:
locatie: als
lon
(longitude) enlat
(latitude).bijvoorbeeld via google maps kun je deze opzoeken
API key
aanmaken als je ingelogd bent bij openweathermap.org
Met de functie set URL
wordt de URL voor het request aangemaakt:
let api_key = "123ff59900abaabab6b523fc8d574208b99";
let lat = 51.3616254;
let lon = 5.4688644;
msg.url = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${api_key}`;
return msg;
(Vul in deze functie je eigen gegevens in, om het te laten werken.)
flow-code sunrise-flow
[
{
"id": "d9e8a0cd873552aa",
"type": "tab",
"label": "sunrise-flow",
"disabled": false,
"info": "",
"env": []
},
{
"id": "35d668045dec97c5",
"type": "inject",
"z": "d9e8a0cd873552aa",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 300,
"y": 140,
"wires": [
[
"01b74c0ff7b988f2"
]
]
},
{
"id": "9b50523b5cd9cd69",
"type": "http request",
"z": "d9e8a0cd873552aa",
"name": "",
"method": "GET",
"ret": "txt",
"paytoqs": false,
"url": "",
"persist": false,
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 610,
"y": 140,
"wires": [
[
"8c8f836cbc5073cf"
]
]
},
{
"id": "01b74c0ff7b988f2",
"type": "function",
"z": "d9e8a0cd873552aa",
"name": "set-url",
"func": "let api_key = \"123ff599008bab6b523fc8d574208bfe\";\nlet lat = 51.3616254;\nlet lon = 5.4688644;\nmsg.url = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${api_key}`;\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 450,
"y": 140,
"wires": [
[
"9b50523b5cd9cd69"
]
]
},
{
"id": "8e1cf206b19362cf",
"type": "debug",
"z": "d9e8a0cd873552aa",
"name": "debug 21",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 860,
"y": 240,
"wires": []
},
{
"id": "8c8f836cbc5073cf",
"type": "json",
"z": "d9e8a0cd873552aa",
"name": "",
"property": "payload",
"action": "",
"pretty": false,
"x": 790,
"y": 140,
"wires": [
[
"8e1cf206b19362cf",
"387dd7d1ddb760b3"
]
]
},
{
"id": "387dd7d1ddb760b3",
"type": "function",
"z": "d9e8a0cd873552aa",
"name": "sunrise",
"func": "let sunrise = msg.payload.sys.sunrise * 1000 ;\nflow.set(\"sunrise\", sunrise);\nmsg.payload = new Date(sunrise);\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 460,
"y": 280,
"wires": [
[
"184c7a3e95b5155b"
]
]
},
{
"id": "184c7a3e95b5155b",
"type": "debug",
"z": "d9e8a0cd873552aa",
"name": "debug 22",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 660,
"y": 280,
"wires": []
},
{
"id": "83d6e6b7ac659c7f",
"type": "inject",
"z": "d9e8a0cd873552aa",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 300,
"y": 360,
"wires": [
[
"699f7036d57d62b9"
]
]
},
{
"id": "699f7036d57d62b9",
"type": "function",
"z": "d9e8a0cd873552aa",
"name": "Date(now)",
"func": "msg.payload = new Date(msg.payload);\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 460,
"y": 360,
"wires": [
[
"e4c2a02a9731aa88"
]
]
},
{
"id": "e4c2a02a9731aa88",
"type": "debug",
"z": "d9e8a0cd873552aa",
"name": "debug 24",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 620,
"y": 360,
"wires": []
}
]
micro:bit radio: best effort communicatie#
De radiocommunicatie in het micro:bit-netwerk is niet volledig betrouwbaar: dit is een “best effort” pakket-communicatie, waarbij er pakketten verloren kunnen gaan.
Eén van de manieren waarop een pakket verloren gaat is als dit “botst” met een ander pakket. Als twee micro:bits kunnen op hetzelfde moment een pakket versturen, dan verstoren deze radiosignalen elkaar, zodat er geen pakket (data) ontvangen kunnen worden. (In technische termen heet dit een collision, ofwel een botsing.) Als de pakketten klein (kort) zijn, en het aantal pakketten per seconde laag, dan is de kans op dergelijke collisions klein. Maar je gebruikt dan maar een beperkt deel van de beschikbare communicatie-capaciteit.
Voor veel vrije frequentiebanden (radiokanalen zonder licenties) geldt een regel van maximaal gebruik, om dergelijke collisions te voorkomen. De 868 MHz band is een voorbeeld van een vrije band; een apparaat mag maximaal 1% van de tijd zenden, met een maximaal vermogen van 25 mW (+14dBm). (Voor sommige delen is deze maximale duty cycle 0,1%.) - zie bijvoorbeeld https://www.ti.com/lit/an/swra048/swra048.pdf
Ook door externe storigen kunnen pakketten verloren raken. Een voorbeeld is het gebruik van de magnetron: deze werkt op 2,4 GHz. Dat is ook de frequentie van WiFi en Bluetooth (en BLE: de micro:bit radio). Een magnetron kan tijdelijk de communicatie storen, waardoor pakketten verloren gaan. (Dit is precies de reden waarom het gebruik van de 2,4 HGz band vrij is, en niet gebonden aan licenties.)
Betrouwbare communicatie#
We hebben gezien dat de radio-communicatie niet helemaal betrouwbaar is: hoe kun je daar toch een betrouwbare communicatie mee maken?
Dit kan op verschillende manieren, en op verschillende plekken in de stack. In het internet werkt dit als volgt:
de basiscommunicatie is een best effort pakket-communicatie. (Dit is de “IP laag”, met het IP protocol; dit is het protocol in het netwerk.)
als een toepassing betrouwbare communicatie nodig heeft, dan kan deze het TCP-protocol gebruiken. Dit protocol wordt uitgevoerd in de eindapparaten (terminal devices ofwel hosts) waarin de toepassing uitgevoerd wordt.
voorbeeld: het web gebruikt het HTTP protocol, op basis van het TCP protocol; deze beide worden op je computer uitgevoerd, en bij de webserver. Het netwerk daartussen kent alleen het IP protocol.
Het TCP-protocol zorgt voor een betrouwbare bytestream-communicatie: de bytes die je verstuurd, worden ook zo bij de ontvangende toepassing afgeleverd. De bytestream wordt in segmenten opgeknipt, deze worden genummerd en met een segment per IP-pakket verstuurd. Als een segment met een bepaald nummer ontvangen is, wordt hiervoor een acknowledgement (bevestiging) naar de afzender gestuurd. Als de afzender deze ontvangstbevestiging niet binnen een bepaalde tijd ontvangt, stuurt deze het betreffende segment nog een keer, net zolang totdat het goed overgekomen is.
In onze IoT-toepassing is betrouwbaarheid niet voor elk bericht van belang:
de sensorberichten worden regelmatig verstuurd, en de (meeste) sensorwaarden veranderen niet zo snel: het is niet zo erg als je zo nu en dan een bericht mist
omdat de sensorberichten genummerd zijn, kun je precies zien welke berichten ontbreken. De sensorwaarden daarvan kun je afschatten door een gemiddelde te nemen van de omliggende sensorwaarden.
berichten van events, zoals het indrukken van een knop, wil je eigenlijk wel betrouwbaar communiceren.
berichten voor het aansturen van actuatoren, zoals het aan- of uitzetten van een lamp, wil je wel betrouwbaar communiceren.
In die gevallen dat er een betrouwbare communicatie nodig is, kunnen we voor dat geval de communicatie betrouwbare maken, bijvoorbeeld door het gebruik van acknowledgements. Dit geeft extra overhead: die is niet nodig bij de meeste berichten.
Vervolgstappen met NodeRed#
Met NodeRed kun je data en diensten in het web gebruiken voor het nemen van beslissingen. Daarmee kun je bijvoorbeeld de regeling van een thermostaat of beregeningsinstallatie gebruik laten maken van de lokale weersverwachting.
NodeRed heeft een grote “flows” library voor het gebruik van dergelijke diensten, en voor het aansturen van allerlei apparaten. Zie: https://flows.nodered.org/ Enkele voorbeelden:
Philips Hue
Ikea Tradfri
Open weather map
(NB: je hebt een API key nodig om die dienst te gebruiken; voor eenvoudig gebruik, tot 60 aanroepen per minuut, is dit gratis)
Enkele praktische punten#
Gebruik in de klas - met grotere groepen
We hebben nog geen experimenten gedaan met een groot aantal microbits in één gateway-netwerk. Als het aantal microbits per gateway te groot wordt, kun je een extra gateway-netwerk gebruiken. Je kunt microbit-netwerken van elkaar scheiden door voor elk netwerk een unieke group te gebruiken. (Zie de microbit Python documentatie.)