Vai al contenuto

Interfacciare Node-RED con un sistema SlimLine

Node-RED è un ambiente di sviluppo visuale open-source progettato per la creazione di flussi di lavoro basati su nodi, comunemente utilizzato per l’automazione e l’integrazione di sistemi IoT (Internet delle cose). Un editor basato su browser permette di interconnettere tra di loro oggetti (Nodi), i nodi sono i mattoncini di base che costituiscono un flusso di lavoro. Ogni nodo esegue una specifica funzione ed esistono moltissimi nodi pronti per l’uso scaricabili direttamente dal web.

Node-RED disponibile anche sul Gateway industriale LoRaWAN Milesight, in questo articolo con il programma dimostrativo Ptp207, vedremo come interfacciare un programma Node-RED con un programma PLC sviluppato con LogicLab.

In Node-RED è stata sviluppata una semplice dashboard con la visualizzazione di 2 LED e la gestione di 2 tasti. La variazione di stato delle variabili sullo SlimLine attiva i LED, la variazione di stato dei tasti attiva variabili nello SlimLine. E’ possibile eseguire il download del programma ed adattarlo alle proprie esigenze.

Node-RED logic I/O dashboard

Descrizione programma di esempio

Per connettere il gateway LoRaWAN ai nostri sistemi programmabili SlimLine possiamo utilizzare diversi metodi, ad esempio il protocollo Modbus, ma un modo più semplice è utilizzare una semplice connessione UDP o TCP, vediamo un esempio che utilizza il nodo tcp request.

Node-RED Flow connessione TCP/IP con SlimLine

Nel flow il nodo tcp request, esegue una connessione in TCP con lo SlimLine ed invia un messaggio JSON con lo stato degli switches della dashboard per il comando delle uscite. In risposta viene ricevuto un messaggio JSON con lo stato degli ingressi che viene decodificato ed appoggiato sui LED della dashboard.

Node-RED flow
[{"id":"9a87f518.8e4c78","type":"tab","label":"SlimLine","disabled":false,"info":""},{"id":"407dbcc8.951b14","type":"comment","z":"9a87f518.8e4c78","name":"Set logic outputs","info":"","x":480,"y":40,"wires":[]},{"id":"70a9f679.7c4d38","type":"function","z":"9a87f518.8e4c78","name":"Set flow variables","func":"flow.set(msg.topic, msg.payload); //Set flow variable\n","outputs":0,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":630,"y":80,"wires":[]},{"id":"ef3e69bf.e53878","type":"ui_switch","z":"9a87f518.8e4c78","name":"Do00","label":"Do00","tooltip":"","group":"7793e86a.8ca5d8","order":3,"width":0,"height":0,"passthru":true,"decouple":"false","topic":"Do00","topicType":"str","style":"","onvalue":"true","onvalueType":"bool","onicon":"","oncolor":"","offvalue":"false","offvalueType":"bool","officon":"","offcolor":"","animate":true,"className":"","x":450,"y":80,"wires":[["70a9f679.7c4d38"]]},{"id":"e6cfa300.ea842","type":"ui_switch","z":"9a87f518.8e4c78","name":"Do01","label":"Do01","tooltip":"","group":"7793e86a.8ca5d8","order":4,"width":0,"height":0,"passthru":true,"decouple":"false","topic":"Do01","topicType":"str","style":"","onvalue":"true","onvalueType":"bool","onicon":"","oncolor":"","offvalue":"false","offvalueType":"bool","officon":"","offcolor":"","animate":true,"className":"","x":450,"y":140,"wires":[["70a9f679.7c4d38"]]},{"id":"1233c636.a225da","type":"comment","z":"9a87f518.8e4c78","name":"Show logic inputs","info":"","x":800,"y":200,"wires":[]},{"id":"c7116466.9c1208","type":"function","z":"9a87f518.8e4c78","name":"Initializing","func":"flow.set('Do00', false); //Set flow variable\nflow.set('Do01', false); //Set flow variable\n","outputs":0,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":260,"y":80,"wires":[]},{"id":"669d70c0.97f0c","type":"inject","z":"9a87f518.8e4c78","name":"Init pulse","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":100,"y":80,"wires":[["c7116466.9c1208"]]},{"id":"7fe96950.b214a8","type":"comment","z":"9a87f518.8e4c78","name":"Flow initializing","info":"Eseguo set uscite logiche","x":100,"y":40,"wires":[]},{"id":"4e674419.00a14c","type":"inject","z":"9a87f518.8e4c78","name":"Clock","props":[{"p":"payload"}],"repeat":"5","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":90,"y":260,"wires":[["767cba89.c7ba74"]]},{"id":"4f015926.a808a8","type":"debug","z":"9a87f518.8e4c78","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":450,"y":320,"wires":[]},{"id":"767cba89.c7ba74","type":"function","z":"9a87f518.8e4c78","name":"JSON Encode","func":"msg.payload='{\"Do00\":'+flow.get('Do00')+',\"Do01\":'+flow.get('Do01')+'}';\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":260,"y":260,"wires":[["16d44eb9.b5b5e1","4f015926.a808a8"]]},{"id":"16d44eb9.b5b5e1","type":"tcp request","z":"9a87f518.8e4c78","name":"Connection","server":"192.168.0.181","port":"3000","out":"time","ret":"buffer","splitc":"1000","newline":"","trim":false,"tls":"","x":470,"y":260,"wires":[["55c19af5.3d3204","186f8eb7.dc8731"]]},{"id":"55c19af5.3d3204","type":"function","z":"9a87f518.8e4c78","name":"","func":"// Convert buffer data to string.\n\nvar RxD=Buffer.from(msg.payload).toString();\nnode.warn(RxD);\n\n// Parse the JSON data.\n\nvar JData = JSON.parse(RxD);\n\nmsg.payload={\"Di00\":JData.Di00, \"Di01\":JData.Di01};\n\nvar msg1={payload:JData.Di00};\nvar msg2={payload:JData.Di01};\nreturn [msg1, msg2];\n","outputs":2,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":680,"y":260,"wires":[["f10e05ca902e8ac3"],["d010fb49f12c0ef4"]]},{"id":"186f8eb7.dc8731","type":"debug","z":"9a87f518.8e4c78","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":650,"y":300,"wires":[]},{"id":"f10e05ca902e8ac3","type":"ui_led","z":"9a87f518.8e4c78","order":1,"group":"7793e86a.8ca5d8","width":0,"height":0,"label":"Di00","labelPlacement":"left","labelAlignment":"left","colorForValue":[{"color":"#808080","value":"false","valueType":"bool"},{"color":"#008000","value":"true","valueType":"bool"}],"allowColorForValueInMessage":false,"shape":"circle","showGlow":true,"name":"Di00","x":830,"y":240,"wires":[]},{"id":"d010fb49f12c0ef4","type":"ui_led","z":"9a87f518.8e4c78","order":2,"group":"7793e86a.8ca5d8","width":0,"height":0,"label":"Di01","labelPlacement":"left","labelAlignment":"left","colorForValue":[{"color":"#808080","value":"false","valueType":"bool"},{"color":"#008000","value":"true","valueType":"bool"}],"allowColorForValueInMessage":false,"shape":"circle","showGlow":true,"name":"Di01","x":830,"y":280,"wires":[]},{"id":"8a68c199ecfbc557","type":"comment","z":"9a87f518.8e4c78","name":"SlimLine TCP/IP communication","info":"","x":450,"y":200,"wires":[]},{"id":"7793e86a.8ca5d8","type":"ui_group","name":"Logic I/O","tab":"b0e50db.8ccdbf","order":1,"disp":true,"width":"6","collapse":false,"className":""},{"id":"b0e50db.8ccdbf","type":"ui_tab","name":"Home","icon":"dashboard","disabled":false,"hidden":false}]

Il programma LogicLab istanzia un server TCP sulla porta 3000 ed attende la ricezione della stringa JSON con il comando delle uscite logiche. Copia lo stato delle uscite invertito sugli ingressi ed invia in risposta una stringa JSON con lo stato degli ingressi.

LogicLab (Ptp207, NodeRED)
PROGRAM NodeRED
VAR CONSTANT
    DBSize : UDINT := 512; (* Rx data size (SysRMAlloc) *)
END_VAR

VAR
    i : UDINT; (* Auxiliary variable *)
    CaseNr : USINT; (* Program case *)
    Fp : eFILEP; (* File pointer array *)
    Do : ARRAY[0..1] OF BOOL; (* Digital outputs *)
    Di : ARRAY[0..1] OF BOOL; (* Digital inputs *)
    TCPServer : SysTCPServer; (* TCPServer management *)
    CLI : CLIServer; (* Command line interface server *)
    DBuf : @STRING; (* Data buffer  (SysRMAlloc) *)
END_VAR

// *****************************************************************************
// PROGRAM "NodeRED"
// *****************************************************************************
// The program accepts a TCP connection, digital outputs is set according the
// json message received. Upon reception the status of logic inputs is sent
// encoded on a json message.
// -----------------------------------------------------------------------------

    // -------------------------------------------------------------------------
    // INITIALIZATION
    // -------------------------------------------------------------------------
    // First program execution loop initializations.

    IF (SysFirstLoop) THEN

        // TCPClient initialization.

        TCPServer.FilesArr:=ADR(Fp); //Files array
        TCPServer.LocalAdd:=ADR('0.0.0.0'); //Local address
        TCPServer.LocalPort:=3000; //Local port
        TCPServer.MaxConn:=1; //Accepted connections
        TCPServer.FlushTm:=50; //Flush time (mS)
        TCPServer.LifeTm:=30; //Life time (S)
        TCPServer.RxSize:=256; //Rx buffer size
        TCPServer.TxSize:=256; //Tx buffer size

        // Command line interface initialization.

        CLI.SpyOn:=FALSE; //Spy On
        CLI.EchoOn:=FALSE; //Echo On
        CLI.CBSize:=512; //Command buffer size
        CLI.CBegin:=ADR('{'); //Command begin
        CLI.CBLength:=Sysstrlen(CLI.CBegin); //Command begin length
        CLI.CEnd:=ADR('}'); //Command end
        CLI.CELength:=Sysstrlen(CLI.CEnd); //Command end length
        CLI.CWTime:=T#100ms; //Character waiting
        CLI.Timeout:=T#1s; //Timeout
    END_IF;

    // Manage the TCP server.

    TCPServer(Enable:=TRUE); //TCPServer management
    CLI(Fp:=Fp); //Command interface server
    IF NOT(SysFIsOpen(Fp)) THEN CaseNr:=0; RETURN; END_IF;

    // -------------------------------------------------------------------------
    // PROGRAM SEQUENCIES
    // -------------------------------------------------------------------------
    // Manage the program sequencies.

    CASE (CaseNr) OF

        // ---------------------------------------------------------------------
        // WAITING THE COMMAND
        // ---------------------------------------------------------------------
        // Waiting the command reception.

        0:
        CLI.Enable:=TRUE; //CLI server enable
        IF (DBuf <> eNULL) THEN i:=SysRMFree(ADR(DBuf)); END_IF;
        IF NOT(CLI.Done) THEN RETURN; END_IF;
        i:=SysWrSpyData(SPY_ASCHEX, 0, 16#00000001, ADR('NodeRED:Rx'), CLI.CBuffer);

        // Received JSON string is decoded.
        // {"Do00":false,"Do01":true}

        i:=JSONDecoder(CLI.CBuffer, ADR('Do00'), BOOL_TYPE, ADR(Do[0]), 1, 0);
        i:=JSONDecoder(CLI.CBuffer, ADR('Do01'), BOOL_TYPE, ADR(Do[1]), 1, 0);
        CLI.Enable:=FALSE; //CLI server enable
        CaseNr:=CaseNr+1; //Program case

        // ---------------------------------------------------------------------
        // Alloc memory buffer and send the answer message.
        // {"Di00":false,"Di01":true}


        1:
        IF NOT(SysRMAlloc(DBSize, ADR(DBuf))) THEN RETURN; END_IF;
        i:=JSONEncoder(DBuf, DBSize, ADR('Di00'), BOOL_TYPE, ADR(Di[0]), 1);
        i:=JSONEncoder(DBuf, DBSize, ADR('Di01'), BOOL_TYPE, ADR(Di[1]), 1);

        i:=SysWrSpyData(SPY_ASCHEX, 0, 16#00000002, ADR('NodeRED:Tx'), DBuf);
        i:=Sysfwrite(DBuf, 1, TO_INT(Sysstrlen(DBuf)), Fp);
        CaseNr:=0; //Program case
    END_CASE;;

// [End of file]
Was this article helpful?