Bijeenkomst 2#

Programma

  • Gateway

    • programmeren van de gateway (meerdere modules)

    • volgen van de logging-berichten op de radio-logger

  • NodeRed

    • elementaire flow(s), oefeningen

    • kopiëren en importeren van een flow (in JSON-formaat)

    • mqtt input, configureren van de broker-security (wordt niet geëxporteerd!)

    • speciale functies voor onze toepasssing - sensoren en actuatoren

  • NodeRed dashboard

    • toevoegen van een extra sensor: lightlevel

Programmeren van de IoT-gateway#

_images/microbit-iot-network.drawio.png

Fig. 11 IoT-netwerk met IoT-knoop(en), IoT-gateway en radio-logger#

De hardware van de IoT-gateway bestaat uit een microbit (V2!), een Elecfreaks iot:bit bordje, en een USB-voeding voor het iot:bit bordje. (De iot:bit heeft een aparte aansluiting voor de USB-voeding; je kunt het bordje niet voeden via de microbit!)

De IoT-gateway verbindt het lokale IoT-netwerk met het internet, via het lokale WiFi netwerk.

_images/iot-protocollen.drawio.png

Fig. 12 IoT-netwerk met IoT-knoop(en), IoT-gateway en radio-logger#

Zie voor het programmeren van de IoT-gateway: Programmeren van de micro:bit als gateway

NodeRed#

Eerste flow#

_images/nodered-first-flow.png

Fig. 13 Eerste flow in NodeRed#

Webserver-flow#

_images/nodered-http-hello.png

Fig. 14 Webserver-flow, met http-opdracht (GET) en URL (/hello)#

Code voor html-template: <h1>Hello World!</h1>

Test met de URL van je NodeRed editor: het deel tot het ?. Bijvoorbeeld: https://elegant-goosander-2583.infvoflows.nl/hello?test=123

Importeren van een flow: via hamburger-menu rechtsboven in NodeRed.

  • Kopieer de onderstaande flow-code (via de “Copy” kno rechtsboven in het uitgeklapte code-blok)

  • Open het import-venster in NodeRed (via het hamburger-menu)

  • Plak de gekopieerde flow in het venster, en klik op Import.

flow-code
[
    {
        "id": "cb507012ba185a11",
        "type": "tab",
        "label": "Flow 6",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "028e671dc5be25f5",
        "type": "template",
        "z": "cb507012ba185a11",
        "name": "",
        "field": "payload",
        "fieldType": "msg",
        "format": "handlebars",
        "syntax": "mustache",
        "template": "<h1>Hello World!</h1>",
        "output": "str",
        "x": 380,
        "y": 620,
        "wires": [
            [
                "25c2ead9cff0d34f"
            ]
        ]
    },
    {
        "id": "1c6c14290c16be21",
        "type": "http in",
        "z": "cb507012ba185a11",
        "name": "",
        "url": "/hello",
        "method": "get",
        "upload": false,
        "swaggerDoc": "",
        "x": 180,
        "y": 620,
        "wires": [
            [
                "028e671dc5be25f5",
                "9ab80f67a0d3e61b"
            ]
        ]
    },
    {
        "id": "25c2ead9cff0d34f",
        "type": "http response",
        "z": "cb507012ba185a11",
        "name": "",
        "statusCode": "",
        "headers": {},
        "x": 570,
        "y": 620,
        "wires": []
    },
    {
        "id": "9ab80f67a0d3e61b",
        "type": "debug",
        "z": "cb507012ba185a11",
        "name": "debug http",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 390,
        "y": 560,
        "wires": []
    }
]

MQTT flow#

MQTT input: sensor payload

_images/nodered-mqtt-debug-sensor-payload.png

Fig. 15 MQTT input: debug sensor payload#

MQTT node parameters (via flow-code hieronder):

  • bekijk de instellingen (parameters) van de MQTT-input-node

    • klik op een node om de instellingen te bekijken en aan te passen

  • Server: infvopedia.nl:1883

  • Topic: node/8c8a/sensors

  • Output: A parsed JSON object

    • datatype:json

MQTT server parameters (via de MQTT-node: potloodje naast Server-selectie)

  • Security (zelf invullen!):

    • username: mqtttest

    • password: abc?

flow-code
[
    {
        "id": "9dc0d8f5a7556d1e",
        "type": "tab",
        "label": "Flow 10",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "53ab0ab2e93ae083",
        "type": "mqtt in",
        "z": "9dc0d8f5a7556d1e",
        "name": "",
        "topic": "node/8c8a/sensors",
        "qos": "2",
        "datatype": "json",
        "broker": "35520d39dbc586cb",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 170,
        "y": 180,
        "wires": [
            [
                "ab167874870a989f"
            ]
        ]
    },
    {
        "id": "ab167874870a989f",
        "type": "debug",
        "z": "9dc0d8f5a7556d1e",
        "name": "debug sensor payload",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 400,
        "y": 180,
        "wires": []
    },
    {
        "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": ""
    }
]

Sensor-functies#

We gebruiken voor alle IoT-nodes dezelfe JSON-layout voor sensoren en actuatoren-payload:

 "payload": {
     "0": {"dOut": 0},      
     "1": {"dOut": 0}, 
     "2": {"dIn": 0},
     "3": {"dIn": 0}, 
     "4": {"temperature": 260},
     "5": {"barometer": 1019},
     "6": {"humidity": 140}
     "7": {"presence": 0},      
     "8": {"aIn": 15}}     

Een JSON object, of het bijbehorende JavaScript, bevat de gegevens van meerdere sensoren. In de NodeRed-flows hebben we voor het dashboard en voor allerlei besturingen de gegevens van de afzonderlijke sensoren nodig. Per sensor hebben we een functie(-node) gedefinieerd die de gegevens van die sensor selecteert, zie de figuur hieronder.

_images/nodered-sensor-actuator-nodes.png

Fig. 16 NodeRed sensor nodes#

De tekst van de temperatuur-functie ziet er als volgt uit:

if (msg.payload.payload[4]) {
  msg.payload = msg.payload.payload[4].temperature / 10;
  return msg;
} else {
  return null;
}

Deze functie-node kun je dan bijvoorbeeld gebruiken in de dashboard-flow.

Sensor function nodes code
[
    {
        "id": "6a34f441bef14c04",
        "type": "tab",
        "label": "Flow 11",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "0459bb20f395f8cb",
        "type": "function",
        "z": "6a34f441bef14c04",
        "name": "4-temperature",
        "func": "if (msg.payload.payload[4]) {\n  msg.payload = msg.payload.payload[4].temperature / 10;\n  return msg;\n} else {\n  return null;\n}",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 260,
        "y": 280,
        "wires": [
            []
        ]
    },
    {
        "id": "a7731a2decf0d455",
        "type": "function",
        "z": "6a34f441bef14c04",
        "name": "5-barometer",
        "func": "if (msg.payload.payload[5]) {\n  msg.payload = msg.payload.payload[5].barometer / 10;\n  return msg;\n} else {\n  return null;\n}",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 250,
        "y": 340,
        "wires": [
            []
        ]
    },
    {
        "id": "719ec1b59c77283b",
        "type": "function",
        "z": "6a34f441bef14c04",
        "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": 250,
        "y": 520,
        "wires": [
            []
        ]
    },
    {
        "id": "c30fec99cf6c8179",
        "type": "function",
        "z": "6a34f441bef14c04",
        "name": "2-dIn",
        "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": 230,
        "y": 160,
        "wires": [
            []
        ]
    },
    {
        "id": "72f4538b210a6294",
        "type": "function",
        "z": "6a34f441bef14c04",
        "name": "3-dIn",
        "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": 230,
        "y": 220,
        "wires": [
            []
        ]
    },
    {
        "id": "ac0afb77869a978f",
        "type": "function",
        "z": "6a34f441bef14c04",
        "name": "7-presence",
        "func": "if (msg.payload.payload[7]) {\n  msg.payload = msg.payload.payload[7].presence;\n  return msg;\n} else {\n  return null;\n}",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 250,
        "y": 460,
        "wires": [
            []
        ]
    },
    {
        "id": "411288406cc12a5a",
        "type": "function",
        "z": "6a34f441bef14c04",
        "name": "6-humidity",
        "func": "if (msg.payload.payload[6]) {\n  msg.payload = msg.payload.payload[6].humidity / 2;\n  return msg;\n} else {\n  return null;\n}",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 250,
        "y": 400,
        "wires": [
            []
        ]
    },
    {
        "id": "c62c75f51c5bb8bc",
        "type": "template",
        "z": "6a34f441bef14c04",
        "name": "0-dOut-on",
        "field": "payload",
        "fieldType": "msg",
        "format": "json",
        "syntax": "mustache",
        "template": "{\"0\": {\"dOut\": 1}}",
        "output": "str",
        "x": 610,
        "y": 160,
        "wires": [
            []
        ]
    },
    {
        "id": "a0220e3959e84953",
        "type": "template",
        "z": "6a34f441bef14c04",
        "name": "0-dOut-off",
        "field": "payload",
        "fieldType": "msg",
        "format": "json",
        "syntax": "mustache",
        "template": "{\"0\": {\"dOut\": 0}}",
        "output": "str",
        "x": 610,
        "y": 220,
        "wires": [
            []
        ]
    },
    {
        "id": "3eead327923f4e02",
        "type": "template",
        "z": "6a34f441bef14c04",
        "name": "1-dOut-on",
        "field": "payload",
        "fieldType": "msg",
        "format": "json",
        "syntax": "mustache",
        "template": "{\"1\": {\"dOut\": 1}}",
        "output": "str",
        "x": 610,
        "y": 280,
        "wires": [
            []
        ]
    },
    {
        "id": "1bfbb39c8fc92766",
        "type": "template",
        "z": "6a34f441bef14c04",
        "name": "1-dOut-off",
        "field": "payload",
        "fieldType": "msg",
        "format": "json",
        "syntax": "mustache",
        "template": "{\"1\": {\"dOut\": 0}}",
        "output": "str",
        "x": 610,
        "y": 340,
        "wires": [
            []
        ]
    }
]

Dashboard-flow#

_images/nodered-temperature-dashboard-flow.png

Fig. 17 Temperatuur dashboard flow#

Installeren van dashboard-nodes#

De bovenstaande flow maakt gebruik van de dashboard-nodes. Met de onderstaande aanwijzingen kun je deze installeren.

  • selecteer in het hamburger-menu rechts: Manage Palette

  • selecteer de tab Install

  • type in het zoekveld: dashboard

  • klik bij node-red-dashboard: Install.

temperatuur dashboard flow
[
    {
        "id": "886e157d7b6324df",
        "type": "tab",
        "label": "Flow 12",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "752cc73ce4821834",
        "type": "mqtt in",
        "z": "886e157d7b6324df",
        "name": "",
        "topic": "node/99f6/sensors",
        "qos": "2",
        "datatype": "json",
        "broker": "35520d39dbc586cb",
        "nl": false,
        "rap": false,
        "inputs": 0,
        "x": 190,
        "y": 180,
        "wires": [
            [
                "dee02a99ffd9d808"
            ]
        ]
    },
    {
        "id": "d453e9b737b7bbba",
        "type": "ui_gauge",
        "z": "886e157d7b6324df",
        "name": "",
        "group": "a4643fc8.e80d68",
        "order": 0,
        "width": 0,
        "height": 0,
        "gtype": "gage",
        "title": "Temperatuur",
        "label": "'C",
        "format": "{{payload}}",
        "min": 0,
        "max": "50",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "diff": false,
        "className": "",
        "x": 650,
        "y": 180,
        "wires": []
    },
    {
        "id": "cf57f073a2389bbb",
        "type": "ui_chart",
        "z": "886e157d7b6324df",
        "name": "",
        "group": "b7537500.9e9de",
        "order": 0,
        "width": 0,
        "height": 0,
        "label": "Temperatuur",
        "chartType": "line",
        "legend": "false",
        "xformat": "HH:mm:ss",
        "interpolate": "linear",
        "nodata": "",
        "dot": false,
        "ymin": "0",
        "ymax": "50",
        "removeOlder": 1,
        "removeOlderPoints": "",
        "removeOlderUnit": "86400",
        "cutout": 0,
        "useOneColor": false,
        "colors": [
            "#1f77b4",
            "#aec7e8",
            "#ff7f0e",
            "#2ca02c",
            "#98df8a",
            "#d62728",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "outputs": 1,
        "x": 650,
        "y": 220,
        "wires": [
            []
        ]
    },
    {
        "id": "dee02a99ffd9d808",
        "type": "function",
        "z": "886e157d7b6324df",
        "name": "4-temperature",
        "func": "if (msg.payload.payload[4]) {\n  msg.payload = msg.payload.payload[4].temperature / 10;\n  return msg;\n} else {\n  return null;\n}",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 440,
        "y": 180,
        "wires": [
            [
                "7a07079123f92475",
                "d453e9b737b7bbba",
                "cf57f073a2389bbb"
            ]
        ]
    },
    {
        "id": "7a07079123f92475",
        "type": "debug",
        "z": "886e157d7b6324df",
        "name": "debug temp",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "{\"temperature\": payload}",
        "targetType": "jsonata",
        "statusVal": "",
        "statusType": "auto",
        "x": 650,
        "y": 120,
        "wires": []
    },
    {
        "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": ""
    },
    {
        "id": "a4643fc8.e80d68",
        "type": "ui_group",
        "name": "My-meters",
        "tab": "4e75c8d2.40f86",
        "order": 1,
        "disp": true,
        "width": "6",
        "collapse": false
    },
    {
        "id": "b7537500.9e9de",
        "type": "ui_group",
        "name": "My-graphs",
        "tab": "4e75c8d2.40f86",
        "order": 2,
        "disp": true,
        "width": "6",
        "collapse": false
    },
    {
        "id": "4e75c8d2.40f86",
        "type": "ui_tab",
        "name": "My-node 99f6",
        "icon": "dashboard",
        "disabled": false,
        "hidden": false
    }
]

Bekijken van het dashboard#

  • open de dashboard-tab in het rechter zij-venster in NodeRed

  • klik op het “uitgaande pijltje” (rechts) voor het openen van het dashboard in de browser.

Opdracht: lichtniveau in het dashboard#

Opdracht

  • bekijk de instellingen van de verschillende dashboard-nodes

    • klik op een node om de instellingen te bekijken en aan te passen

  • voeg een extra dashboard-flow toe, voor het lichtniveau (sensor/kanaal 8)

Dashboard-flow code
[
    {
        "id": "886e157d7b6324df",
        "type": "tab",
        "label": "Flow 12",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "752cc73ce4821834",
        "type": "mqtt in",
        "z": "886e157d7b6324df",
        "name": "",
        "topic": "node/99f6/sensors",
        "qos": "2",
        "datatype": "json",
        "broker": "35520d39dbc586cb",
        "nl": false,
        "rap": false,
        "inputs": 0,
        "x": 190,
        "y": 180,
        "wires": [
            [
                "dee02a99ffd9d808"
            ]
        ]
    },
    {
        "id": "d453e9b737b7bbba",
        "type": "ui_gauge",
        "z": "886e157d7b6324df",
        "name": "",
        "group": "a4643fc8.e80d68",
        "order": 0,
        "width": 0,
        "height": 0,
        "gtype": "gage",
        "title": "Temperatuur",
        "label": "'C",
        "format": "{{payload}}",
        "min": 0,
        "max": "50",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "diff": false,
        "className": "",
        "x": 650,
        "y": 180,
        "wires": []
    },
    {
        "id": "cf57f073a2389bbb",
        "type": "ui_chart",
        "z": "886e157d7b6324df",
        "name": "",
        "group": "b7537500.9e9de",
        "order": 0,
        "width": 0,
        "height": 0,
        "label": "Temperatuur",
        "chartType": "line",
        "legend": "false",
        "xformat": "HH:mm:ss",
        "interpolate": "linear",
        "nodata": "",
        "dot": false,
        "ymin": "0",
        "ymax": "50",
        "removeOlder": 1,
        "removeOlderPoints": "",
        "removeOlderUnit": "86400",
        "cutout": 0,
        "useOneColor": false,
        "colors": [
            "#1f77b4",
            "#aec7e8",
            "#ff7f0e",
            "#2ca02c",
            "#98df8a",
            "#d62728",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "outputs": 1,
        "x": 650,
        "y": 220,
        "wires": [
            []
        ]
    },
    {
        "id": "dee02a99ffd9d808",
        "type": "function",
        "z": "886e157d7b6324df",
        "name": "4-temperature",
        "func": "if (msg.payload.payload[4]) {\n  msg.payload = msg.payload.payload[4].temperature / 10;\n  return msg;\n} else {\n  return null;\n}",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 440,
        "y": 180,
        "wires": [
            [
                "7a07079123f92475",
                "d453e9b737b7bbba",
                "cf57f073a2389bbb"
            ]
        ]
    },
    {
        "id": "7a07079123f92475",
        "type": "debug",
        "z": "886e157d7b6324df",
        "name": "debug temp",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "{\"temperature\": payload}",
        "targetType": "jsonata",
        "statusVal": "",
        "statusType": "auto",
        "x": 650,
        "y": 120,
        "wires": []
    },
    {
        "id": "7de65936a99884a8",
        "type": "ui_gauge",
        "z": "886e157d7b6324df",
        "name": "",
        "group": "a4643fc8.e80d68",
        "order": 0,
        "width": 0,
        "height": 0,
        "gtype": "gage",
        "title": "Licht",
        "label": "units",
        "format": "{{payload}}",
        "min": "0",
        "max": "1023",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "x": 630,
        "y": 300,
        "wires": []
    },
    {
        "id": "a0fed3c53cf4f8bb",
        "type": "ui_chart",
        "z": "886e157d7b6324df",
        "name": "",
        "group": "b7537500.9e9de",
        "order": 0,
        "width": 0,
        "height": 0,
        "label": "Licht",
        "chartType": "line",
        "legend": "false",
        "xformat": "HH:mm:ss",
        "interpolate": "linear",
        "nodata": "",
        "dot": false,
        "ymin": "0",
        "ymax": "1023",
        "removeOlder": 1,
        "removeOlderPoints": "",
        "removeOlderUnit": "86400",
        "cutout": 0,
        "useOneColor": false,
        "colors": [
            "#1f77b4",
            "#aec7e8",
            "#ff7f0e",
            "#2ca02c",
            "#98df8a",
            "#d62728",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "outputs": 2,
        "x": 630,
        "y": 340,
        "wires": [
            [],
            []
        ]
    },
    {
        "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": ""
    },
    {
        "id": "a4643fc8.e80d68",
        "type": "ui_group",
        "name": "My-meters",
        "tab": "4e75c8d2.40f86",
        "order": 1,
        "disp": true,
        "width": "6",
        "collapse": false
    },
    {
        "id": "b7537500.9e9de",
        "type": "ui_group",
        "name": "My-graphs",
        "tab": "4e75c8d2.40f86",
        "order": 2,
        "disp": true,
        "width": "6",
        "collapse": false
    },
    {
        "id": "4e75c8d2.40f86",
        "type": "ui_tab",
        "name": "My-node 99f6",
        "icon": "dashboard",
        "disabled": false,
        "hidden": false
    }
]

Actuator-flows#

We hebben een aantal nodes gedefinieerd voor het aansturen van de LEDs (display van de micro:bit).

_images/nodered-actuator-nodes.png

Fig. 18 Nodes voor het aansturen van de IoT-knoop LEDs (of display)#

De tekst van de “0-dOut-on” node is:

{"0": {"dOut": 1}}

De tekst van de andere nodes is analoog.

Deze nodes kun je in een actuator-flow gebruiken, met een MQTT-output-node als bestemming

_images/nodered-actuator-on-flow.png

Fig. 19 Gedeeltelijke actuator-flow#

Opdracht

  • maak deze flow voor je eigen node(s)

  • breid deze flow uit met een “off” flow