Node-RED - wykorzystanie protokołu Modbus TCP

20.11.2019 How to / Sterowanie i akwizycja danych
Node-RED - wykorzystanie protokołu Modbus TCP
Wizerunek autora
Producent: MOXA
  • Zakłady przemysłowe

Jest to kolejny post odnośnie wykorzystania oprogramowania Node-RED do wizualizacji parametrów i sygnałów. Tym razem omówimy implementację biblioteki, która pozwoli odpytywać urządzenia Modbus TCP, a następnie wizualizować zbierane dane pomiarowe.

W poprzednim artykule opisaliśmy czym jest Node-RED oraz w jaki sposób zainstalować to oprogramowanie na komputerach przemysłowych Moxa.

Topologia testowanego układu

Na potrzeby tego artykułu została zbudowana topologia, której schemat ideowy został przedstawiony na poniższym rysunku.

 

  • ioThinx 4510 - pełni rolę modułowego systemu do zbierania sygnałów pomiarowych. Baza uzupełniona jest o dodatkowe moduły: 45MR-2606 wyposażony w 8 wejść i 8 wyjść cyfrowych oraz 45MR-3810 wyposażony w 8 wejść analogowych. IoThinx 4510 dodatkowo ma włączoną funkcję Modbus TCP Slave.
  • UC-3100 - komputer przemysłowy z procesorem ARM na szynę DIN na którym jest zainstalowane oprogramowanie Node-RED.
  • Dashboard służący do monitorowania parametrów i sterowania sygnałami wyjściowymi podłączonymi do ioThinx 4510.

Instalacja dodatkowych bibliotek

Aby w pełni zbudować nasz zestaw Demo, musimy zainstalować dodatkowe biblioteki umożliwiające tworzenie wizualizacji oraz obsługujące funkcje Modbus TCP.

Możemy to zrobić w dwojaki sposób - z poziomu command line systemu Linux używając NPM, ale zdecydowanie wygodniej wykorzystać do tego celu interfejs Node-RED.

W prawym górnym rogu ekranu, należy przejść do zakładki Manage palette.

Aby zainstalować funkcję "Dashboard" należy przejść do zakładki Install, a następnie wyszukać funkcję "Dashboard". W naszym demo wykorzystujemy funkcję node-red-dashboard w wersji 2.17.1. Klikając polecenie "Install" zainstalujemy wspomnianą funkcję. Dodatkowo, możemy zajrzeć do dokumentacji biblioteki, aby sprawdzić działanie każdego bloczku oraz przykłady użycia.

Podobnie postępujemy z instalacją funkcji Modbus TCP. W naszym przypadku wykorzystujemy bibliotekę node-red-contrib-modbustcp w wersji 1.2.3.

Tworzenie prostego flow oraz dashboardu dla ioThinx 4510

Nasz finalny program (flow) przedstawiony jest poniżej

Grupy

Na początku zaczniemy od utworzenia grup dla graficznego interfejsu

  1. Kliknij ikonę Dashboard
  2. Kliknij +tab ->Tab 1 -> Edit, a następnie zmień nazwę np. na Modbus TCP Demo -> kliknij Update
  3. Kliknij Modbus TCP Demo-> kliknij +group 4 razy, aby utworzyć 4 grupy -> zmień nazwy utworzonych grup

Odczyt wejść cyfrowych

Teraz utworzymy program do odczytu stanu wejść cyfrowych ioThinx 4510.

Skąd wiemy, które rejestry musimy odpytywać? W konsoli webowej ioThinx 4510 w zakładce Protocol --> Modbus, w przejrzysty sposób wyszczególnione są wszystkie rejestry.

Wybierz node input-->modbustcp i przeciągnij do pola edycji.

Podwójne kliknięcie myszki na przeniesiony bloczek otworzy panel edycji, który należy wypełnić zgodnie z poniższym zdjęciem. W polu " Quantity" możemy wpisać pojedynczy rejestr, aby odczytywać każdą wartość oddzielnie, ale również możemy wpisać inną wartość, jeżeli chcemy jednocześnie odczytywać większą liczbę rejestrów. W naszym przypadku tę wartość ustawiliśmy na 3, gdyż odczytujemy wartości 3 rejestrów.

 

Dodajemy serwer TCP, wpisując adres IP oraz numer portu.

Następnie, aby wyodrębnić wartości pojedynczego rejestru musimy dopisać prostą funkcje. Do tego celu wykorzystujemy bloczek "function".

%rejestr 1
var o = msg.payload
msg.payload = o[0];
return msg;

%rejestr 2
var o = msg.payload
msg.payload = o[1];
return msg;

%rejestr 3
var o = msg.payload
msg.payload = o[2];
return msg;

Odczytane wartości możemy przypisać do diody LED, która sygnalizuje status. W tym celu możemy użyć bloczka "LED" .

Poniżej znajduje się panel konfiguracyjny diody LED. Możemy tutaj przypisać bloczek do odpowiedniej grupy w wizualizacji oraz zdefiniować rozmiar, kolory itp.

Odczyt wejść analogowych

Analogicznie postępujemy w przypadku odczytu stanu wejść analogowych.

Dodatkowo, możemy z wykorzystaniem bloczek "Range", aby przeskalować wskazane wartości.

A następnie wygenerować wykresy z użyciem bloczku "Chart".

Zmiana stanu wyjść cyfrowych

W tym przypadku wykorzystujemy funkcje z grupy output -> modbustcp.

Po przeniesieniu w pole edycji funkcji, edytujemy ją zgodnie z poniższym obrazkiem.

Natomiast na wejście dołączamy bloczek "Switch".

Pozostałe przyciski dodajemy analogicznie.

Jeżeli wszystko zostało poprawnie skonfigurowane, należy kliknąć przycisk "Deploy" aby nasze ustawienia zostały przetworzone.

Gotowy kod do pobrania

Przechodząc do zakładki Import można dodać kod programu, który wykorzystywany jest w naszym demo z Modbusem TCP.

Kod do pobrania niżej.

Kod do pobrania
[
    {
        "id": "fb8e21fd.e3aef8",
        "type": "function",
        "z": "d51b82ff.d571c",
        "name": "DI0",
        "func": "var o = msg.payload\nmsg.payload = o[0];\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 420,
        "y": 180,
        "wires": [
            [
                "3d9bf47e.ebdb8c"
            ]
        ]
    },
    {
        "id": "3d9bf47e.ebdb8c",
        "type": "ui_led",
        "z": "d51b82ff.d571c",
        "group": "2190500d.aeda5",
        "order": 0,
        "width": 0,
        "height": 0,
        "label": "Otwarcie szafki",
        "labelPlacement": "left",
        "labelAlignment": "left",
        "colorForValue": [
            {
                "color": "red",
                "value": "true",
                "valueType": "bool"
            },
            {
                "color": "grey",
                "value": "true",
                "valueType": "bool"
            }
        ],
        "name": "Otwarcie szafki",
        "x": 690,
        "y": 180,
        "wires": []
    },
    {
        "id": "7bcb6f54.20ab6",
        "type": "function",
        "z": "d51b82ff.d571c",
        "name": "DI1",
        "func": "var o = msg.payload\nmsg.payload = o[1];\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 430,
        "y": 240,
        "wires": [
            [
                "c1bca2aa.fd195"
            ]
        ]
    },
    {
        "id": "c1bca2aa.fd195",
        "type": "ui_led",
        "z": "d51b82ff.d571c",
        "group": "2190500d.aeda5",
        "order": 0,
        "width": 0,
        "height": 0,
        "label": "Alarm",
        "labelPlacement": "left",
        "labelAlignment": "left",
        "colorForValue": [
            {
                "color": "orange",
                "value": "true",
                "valueType": "bool"
            },
            {
                "color": "grey",
                "value": "true",
                "valueType": "bool"
            }
        ],
        "name": "Alarm",
        "x": 670,
        "y": 240,
        "wires": []
    },
    {
        "id": "a0925546.7a82c",
        "type": "function",
        "z": "d51b82ff.d571c",
        "name": "DI2",
        "func": "var o = msg.payload\nmsg.payload = o[2];\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 430,
        "y": 300,
        "wires": [
            [
                "7037ded.edfd3a"
            ]
        ]
    },
    {
        "id": "7037ded.edfd3a",
        "type": "ui_led",
        "z": "d51b82ff.d571c",
        "group": "2190500d.aeda5",
        "order": 0,
        "width": 0,
        "height": 0,
        "label": "Ostrzeżenie",
        "labelPlacement": "left",
        "labelAlignment": "left",
        "colorForValue": [
            {
                "color": "green",
                "value": "true",
                "valueType": "bool"
            },
            {
                "color": "grey",
                "value": "true",
                "valueType": "bool"
            }
        ],
        "name": "Ostrzeżenie",
        "x": 690,
        "y": 300,
        "wires": []
    },
    {
        "id": "63e23a66.cad09c",
        "type": "modbustcp-read",
        "z": "d51b82ff.d571c",
        "name": "Digital inputs",
        "topic": "test",
        "dataType": "Input",
        "adr": "0000",
        "quantity": "3",
        "rate": "1",
        "rateUnit": "s",
        "server": "bef14afc.b9a8b8",
        "ieeeType": "off",
        "ieeeBE": true,
        "x": 250,
        "y": 280,
        "wires": [
            [
                "fb8e21fd.e3aef8",
                "7bcb6f54.20ab6",
                "a0925546.7a82c"
            ]
        ]
    },
    {
        "id": "37a0ea77.6d539e",
        "type": "modbustcp-write",
        "z": "d51b82ff.d571c",
        "name": "czerwony",
        "topic": "czerwony",
        "dataType": "Coil",
        "adr": "0018",
        "server": "bef14afc.b9a8b8",
        "x": 1280,
        "y": 180,
        "wires": []
    },
    {
        "id": "e355e276.76eea8",
        "type": "ui_switch",
        "z": "d51b82ff.d571c",
        "name": "",
        "label": "Wlacz/wylacz silnik 1",
        "tooltip": "",
        "group": "41230c9e.b3f974",
        "order": 1,
        "width": 0,
        "height": 0,
        "passthru": true,
        "decouple": "false",
        "topic": "",
        "style": "",
        "onvalue": "1",
        "onvalueType": "num",
        "onicon": "",
        "oncolor": "",
        "offvalue": "0",
        "offvalueType": "num",
        "officon": "",
        "offcolor": "",
        "x": 1040,
        "y": 180,
        "wires": [
            [
                "37a0ea77.6d539e"
            ]
        ]
    },
    {
        "id": "a06beeae.1fa0b8",
        "type": "ui_switch",
        "z": "d51b82ff.d571c",
        "name": "",
        "label": "Wlacz/wylacz silnik 2",
        "tooltip": "",
        "group": "41230c9e.b3f974",
        "order": 1,
        "width": 0,
        "height": 0,
        "passthru": true,
        "decouple": "false",
        "topic": "",
        "style": "",
        "onvalue": "1",
        "onvalueType": "num",
        "onicon": "",
        "oncolor": "",
        "offvalue": "0",
        "offvalueType": "num",
        "officon": "",
        "offcolor": "",
        "x": 1040,
        "y": 260,
        "wires": [
            [
                "d211a0c7.414b08"
            ]
        ]
    },
    {
        "id": "8b2de642.d72288",
        "type": "ui_switch",
        "z": "d51b82ff.d571c",
        "name": "",
        "label": "Wlacz/wylacz silnik 3",
        "tooltip": "",
        "group": "41230c9e.b3f974",
        "order": 1,
        "width": 0,
        "height": 0,
        "passthru": true,
        "decouple": "false",
        "topic": "",
        "style": "",
        "onvalue": "1",
        "onvalueType": "num",
        "onicon": "",
        "oncolor": "",
        "offvalue": "0",
        "offvalueType": "num",
        "officon": "",
        "offcolor": "",
        "x": 1040,
        "y": 340,
        "wires": [
            [
                "20b93a94.7534e6"
            ]
        ]
    },
    {
        "id": "20b93a94.7534e6",
        "type": "modbustcp-write",
        "z": "d51b82ff.d571c",
        "name": "zielony",
        "topic": "zielony",
        "dataType": "Coil",
        "adr": "0016",
        "server": "bef14afc.b9a8b8",
        "x": 1280,
        "y": 380,
        "wires": []
    },
    {
        "id": "d211a0c7.414b08",
        "type": "modbustcp-write",
        "z": "d51b82ff.d571c",
        "name": "pomaranczowy",
        "topic": "pomaranczowy",
        "dataType": "Coil",
        "adr": "0017",
        "server": "bef14afc.b9a8b8",
        "x": 1276.88330078125,
        "y": 273.88330078125,
        "wires": []
    },
    {
        "id": "d17184a1.a600f",
        "type": "modbustcp-read",
        "z": "d51b82ff.d571c",
        "name": "Analog inputs",
        "topic": "analog",
        "dataType": "InputRegister",
        "adr": "0008",
        "quantity": "3",
        "rate": "1",
        "rateUnit": "s",
        "server": "9e0a926.fee177",
        "ieeeType": "off",
        "ieeeBE": true,
        "x": 240,
        "y": 620,
        "wires": [
            [
                "eda4c37a.5ec82",
                "76c278c9.6209e8",
                "7badb99b.7e2178"
            ]
        ]
    },
    {
        "id": "eda4c37a.5ec82",
        "type": "function",
        "z": "d51b82ff.d571c",
        "name": "Pompa 1",
        "func": "var o = msg.payload\nmsg.payload = o[0];\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 480,
        "y": 540,
        "wires": [
            [
                "999d3279.66d638"
            ]
        ]
    },
    {
        "id": "76c278c9.6209e8",
        "type": "function",
        "z": "d51b82ff.d571c",
        "name": "Pompa 2",
        "func": "var o = msg.payload\nmsg.payload = o[1];\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 490,
        "y": 600,
        "wires": [
            [
                "b80a0eb0.b18c38"
            ]
        ]
    },
    {
        "id": "7badb99b.7e2178",
        "type": "function",
        "z": "d51b82ff.d571c",
        "name": "Pompa 3",
        "func": "var o = msg.payload\nmsg.payload = o[2];\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 490,
        "y": 660,
        "wires": [
            [
                "330b3146.afe62e"
            ]
        ]
    },
    {
        "id": "98f0f0e7.49fc4",
        "type": "ui_gauge",
        "z": "d51b82ff.d571c",
        "name": "Wydajność Pompy 1",
        "group": "d3bd6514.c634e",
        "order": 2,
        "width": "6",
        "height": "4",
        "gtype": "gage",
        "title": "Wydajność Pompy 1",
        "label": "m3/s",
        "format": "{{value}}",
        "min": 0,
        "max": "100",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "x": 980,
        "y": 460,
        "wires": []
    },
    {
        "id": "90ea5f69.98f6b",
        "type": "ui_gauge",
        "z": "d51b82ff.d571c",
        "name": "Wydajność Pompy 2",
        "group": "d3bd6514.c634e",
        "order": 2,
        "width": 0,
        "height": 0,
        "gtype": "gage",
        "title": "Wydajność Pompy 2",
        "label": "m3/s",
        "format": "{{value}}",
        "min": 0,
        "max": "65",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "x": 980,
        "y": 500,
        "wires": []
    },
    {
        "id": "83248585.b2ee78",
        "type": "ui_gauge",
        "z": "d51b82ff.d571c",
        "name": "Wydajność Pompy 3",
        "group": "d3bd6514.c634e",
        "order": 2,
        "width": 0,
        "height": 0,
        "gtype": "gage",
        "title": "Wydajność Pompy 3",
        "label": "m3/s",
        "format": "{{value}}",
        "min": 0,
        "max": "35",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "x": 980,
        "y": 540,
        "wires": []
    },
    {
        "id": "999d3279.66d638",
        "type": "range",
        "z": "d51b82ff.d571c",
        "minin": "0",
        "maxin": "65535",
        "minout": "0",
        "maxout": "100",
        "action": "clamp",
        "round": true,
        "property": "payload",
        "name": "Pompa 1",
        "x": 720,
        "y": 540,
        "wires": [
            [
                "98f0f0e7.49fc4",
                "6c5ed0a4.acdec"
            ]
        ]
    },
    {
        "id": "b80a0eb0.b18c38",
        "type": "range",
        "z": "d51b82ff.d571c",
        "minin": "0",
        "maxin": "65535",
        "minout": "0",
        "maxout": "100",
        "action": "clamp",
        "round": true,
        "property": "payload",
        "name": "Pompa 2",
        "x": 720,
        "y": 600,
        "wires": [
            [
                "90ea5f69.98f6b",
                "91b46205.524578"
            ]
        ]
    },
    {
        "id": "330b3146.afe62e",
        "type": "range",
        "z": "d51b82ff.d571c",
        "minin": "0",
        "maxin": "65535",
        "minout": "0",
        "maxout": "100",
        "action": "clamp",
        "round": true,
        "property": "payload",
        "name": "Pompa 3",
        "x": 720,
        "y": 660,
        "wires": [
            [
                "83248585.b2ee78",
                "33557ad0.3e59ae"
            ]
        ]
    },
    {
        "id": "6c5ed0a4.acdec",
        "type": "ui_chart",
        "z": "d51b82ff.d571c",
        "name": "",
        "group": "5a921b3d.03261c",
        "order": 5,
        "width": "10",
        "height": "7",
        "label": "Pompa 1",
        "chartType": "line",
        "legend": "false",
        "xformat": "HH:mm",
        "interpolate": "linear",
        "nodata": "Brak danych",
        "dot": true,
        "ymin": "-10",
        "ymax": "120",
        "removeOlder": "3",
        "removeOlderPoints": "",
        "removeOlderUnit": "60",
        "cutout": 0,
        "useOneColor": false,
        "colors": [
            "#0080ff",
            "#ff8000",
            "#00ff00",
            "#2ca02c",
            "#98df8a",
            "#d62728",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "useOldStyle": false,
        "outputs": 1,
        "x": 1020,
        "y": 600,
        "wires": [
            []
        ]
    },
    {
        "id": "936959ac.834718",
        "type": "ui_button",
        "z": "d51b82ff.d571c",
        "name": "",
        "group": "5a921b3d.03261c",
        "order": 4,
        "width": "2",
        "height": "1",
        "passthru": false,
        "label": "Reset",
        "tooltip": "",
        "color": "",
        "bgcolor": "",
        "icon": "",
        "payload": "[]",
        "payloadType": "json",
        "topic": "",
        "x": 750,
        "y": 820,
        "wires": [
            [
                "6c5ed0a4.acdec",
                "91b46205.524578",
                "33557ad0.3e59ae"
            ]
        ]
    },
    {
        "id": "91b46205.524578",
        "type": "ui_chart",
        "z": "d51b82ff.d571c",
        "name": "",
        "group": "5a921b3d.03261c",
        "order": 5,
        "width": "10",
        "height": "7",
        "label": "Pompa 2",
        "chartType": "line",
        "legend": "false",
        "xformat": "HH:mm",
        "interpolate": "linear",
        "nodata": "Brak danych",
        "dot": true,
        "ymin": "-10",
        "ymax": "70",
        "removeOlder": "3",
        "removeOlderPoints": "",
        "removeOlderUnit": "60",
        "cutout": 0,
        "useOneColor": false,
        "colors": [
            "#ff0000",
            "#ff8000",
            "#00ff00",
            "#2ca02c",
            "#98df8a",
            "#d62728",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "useOldStyle": false,
        "outputs": 1,
        "x": 1020,
        "y": 660,
        "wires": [
            []
        ]
    },
    {
        "id": "33557ad0.3e59ae",
        "type": "ui_chart",
        "z": "d51b82ff.d571c",
        "name": "",
        "group": "5a921b3d.03261c",
        "order": 5,
        "width": "10",
        "height": "7",
        "label": "Pompa 3",
        "chartType": "line",
        "legend": "false",
        "xformat": "HH:mm",
        "interpolate": "linear",
        "nodata": "Brak danych",
        "dot": true,
        "ymin": "-10",
        "ymax": "40",
        "removeOlder": "3",
        "removeOlderPoints": "",
        "removeOlderUnit": "60",
        "cutout": 0,
        "useOneColor": false,
        "colors": [
            "#00ff00",
            "#ff8000",
            "#00ff00",
            "#2ca02c",
            "#98df8a",
            "#d62728",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "useOldStyle": false,
        "outputs": 1,
        "x": 1020,
        "y": 720,
        "wires": [
            []
        ]
    },
    {
        "id": "2190500d.aeda5",
        "type": "ui_group",
        "z": "",
        "name": "Monitorowanie",
        "tab": "c92c45be.e0d648",
        "order": 1,
        "disp": true,
        "width": "6",
        "collapse": false
    },
    {
        "id": "bef14afc.b9a8b8",
        "type": "modbustcp-server",
        "z": "",
        "name": "iothinx",
        "host": "192.168.3.214",
        "port": "502",
        "unit_id": "2",
        "reconnecttimeout": ""
    },
    {
        "id": "41230c9e.b3f974",
        "type": "ui_group",
        "z": "",
        "name": "Sterowanie silnikami",
        "tab": "c92c45be.e0d648",
        "order": 2,
        "disp": true,
        "width": "6",
        "collapse": false
    },
    {
        "id": "9e0a926.fee177",
        "type": "modbustcp-server",
        "z": "",
        "name": "analog",
        "host": "192.168.3.214",
        "port": "502",
        "unit_id": "3",
        "reconnecttimeout": ""
    },
    {
        "id": "d3bd6514.c634e",
        "type": "ui_group",
        "z": "",
        "name": "Wydajność Pomp",
        "tab": "c92c45be.e0d648",
        "disp": true,
        "width": "6",
        "collapse": false
    },
    {
        "id": "5a921b3d.03261c",
        "type": "ui_group",
        "z": "",
        "name": "Cisnienie",
        "tab": "c92c45be.e0d648",
        "disp": true,
        "width": "10",
        "collapse": false
    },
    {
        "id": "c92c45be.e0d648",
        "type": "ui_tab",
        "z": "",
        "name": "Modbus TCP Demo",
        "icon": "dashboard",
        "order": 2,
        "disabled": false,
        "hidden": false
    }
]

Dashboard

Wpisując w przeglądarce adres <adres IP komputera>:1880/UI uzyskamy dostęp do graficznego Dashboardu z którego poziomu możemy monitorować status wejść urządzenia, a także sterować jego wyjściami.

Podsumowanie

W powyższym wpisie pokazaliśmy, że w szybki sposób można praktycznie za darmo zbudować prostą aplikację do monitorowania np. procesu produkcyjnego. Oczywiście omówiliśmy bardzo podstawowe funkcje. Node-RED oferuje zdecydowanie więcej możliwości, np. wysyłanie powiadomień sms/mail o awaryjnych stanach urządzenia, a także wiele więcej. Szczegóły można znaleźć na stronie Node-RED:
https://nodered.org/

Innym ciekawym przykładem jest wykorzystanie protokołu MQTT. Więcej szczegółów o implementacji protokołu MQTT na modułowym kontrolerze pomiarowym ioThinx 4510 można znaleźć w poniższym wpisie

http://moxa.elmark.com.pl/2019/09/19/mqtt-iothinx/

W przypadku pytań, można się z nami skontaktować drogą mailową: moxa@elmark.com.pl

Skontaktuj się ze specjalistą Elmark

Masz pytania? Potrzebujesz porady? Zadzwoń lub napisz do nas!