edge lab LIVE! starts Tuesday in Munich. Grab a workshop seat and take home the board you build on. Code FRIEND-HACKSTER saves 20%. () Learn more!
Parshwa Vinod Patil
Published

IIoT: PLC Data Visualization with ThingsBoard & Node-RED

A complete guide to bridging the IT/OT gap by connecting a Siemens S7-1200 PLC to ThingsBoard Cloud for bi-directional remote control.

BeginnerFull instructions provided6 hours99
IIoT: PLC Data Visualization with ThingsBoard & Node-RED

Things used in this project

Hardware components

Siemens- SIMATIC S7-1200
×1

Software apps and online services

Siemens TIA Portal v19
Node-RED
Node-RED
ThingsBoard

Hand tools and fabrication machines

PROFINET LAN
24 V SMPS

Story

Read more

Schematics

The Hardware Wiring

The IIoT Architecture

Steps guide

Code

Node-red Flow

JSON
Just Import the flow in node-red.
[
    {
        "id": "e161f68f89d3ac03",
        "type": "tab",
        "label": "ThingsBoard Documnetation",
        "disabled": true,
        "info": "",
        "env": []
    },
    {
        "id": "0d8b03d494a4f2ce",
        "type": "s7 in",
        "z": "e161f68f89d3ac03",
        "endpoint": "e90cf4651fb8c8e1",
        "mode": "single",
        "variable": "Test",
        "diff": true,
        "name": "",
        "x": 1150,
        "y": 40,
        "wires": [
            [
                "5c5e5185a78a5cf0"
            ]
        ]
    },
    {
        "id": "5c5e5185a78a5cf0",
        "type": "function",
        "z": "e161f68f89d3ac03",
        "name": "function 1",
        "func": "msg.payload = {temperature: msg.payload};\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1320,
        "y": 40,
        "wires": [
            [
                "1bf71d775e71942f"
            ]
        ]
    },
    {
        "id": "1bf71d775e71942f",
        "type": "mqtt out",
        "z": "e161f68f89d3ac03",
        "name": "",
        "topic": "v1/devices/me/telemetry",
        "qos": "0",
        "retain": "",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "a04dcd93b1f95dc0",
        "x": 1550,
        "y": 40,
        "wires": []
    },
    {
        "id": "062ab7e7467af1b5",
        "type": "mqtt in",
        "z": "e161f68f89d3ac03",
        "name": "",
        "topic": "v1/devices/me/rpc/request/+",
        "qos": "0",
        "datatype": "auto-detect",
        "broker": "a04dcd93b1f95dc0",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 1200,
        "y": 200,
        "wires": [
            [
                "fd01730b5d528185"
            ]
        ]
    },
    {
        "id": "fd01730b5d528185",
        "type": "function",
        "z": "e161f68f89d3ac03",
        "name": "function 2",
        "func": "// 1. Parse the incoming ThingsBoard RPC message\nlet data = msg.payload;\n\n// 2. Check if the RPC method is \"setState\"\nif (data.method === \"setState\") {\n    // 3. Extract the boolean param (true/false)\n    msg.payload = data.params; \n    \n    // Optional: Set the topic if your S7 node is configured for multiple variables\n    // msg.topic = \"MyPLCVariable\"; \n    \n    return msg;\n}\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1440,
        "y": 200,
        "wires": [
            [
                "74b557191dfa2b09"
            ]
        ]
    },
    {
        "id": "74b557191dfa2b09",
        "type": "s7 out",
        "z": "e161f68f89d3ac03",
        "endpoint": "e90cf4651fb8c8e1",
        "variable": "Start/Stop",
        "name": "",
        "x": 1640,
        "y": 200,
        "wires": []
    },
    {
        "id": "e90cf4651fb8c8e1",
        "type": "s7 endpoint",
        "transport": "iso-on-tcp",
        "address": "192.168.0.1",
        "port": "102",
        "rack": "0",
        "slot": "1",
        "localtsaphi": "01",
        "localtsaplo": "00",
        "remotetsaphi": "01",
        "remotetsaplo": "00",
        "connmode": "rack-slot",
        "adapter": "",
        "busaddr": 2,
        "cycletime": 1000,
        "timeout": 2000,
        "name": "",
        "vartable": [
            {
                "addr": "MI10",
                "name": "Test"
            },
            {
                "addr": "M0.0",
                "name": "Start/Stop"
            }
        ]
    },
    {
        "id": "a04dcd93b1f95dc0",
        "type": "mqtt-broker",
        "name": "",
        "broker": "thingsboard.cloud",
        "port": "1883",
        "clientid": "",
        "autoConnect": true,
        "usetls": false,
        "protocolVersion": 4,
        "keepalive": 60,
        "cleansession": true,
        "autoUnsubscribe": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthRetain": "false",
        "birthPayload": "",
        "birthMsg": {},
        "closeTopic": "",
        "closeQos": "0",
        "closeRetain": "false",
        "closePayload": "",
        "closeMsg": {},
        "willTopic": "",
        "willQos": "0",
        "willRetain": "false",
        "willPayload": "",
        "willMsg": {},
        "userProps": "",
        "sessionExpiry": ""
    },
    {
        "id": "3dd7cc63551b0332",
        "type": "global-config",
        "env": [],
        "modules": {
            "node-red-contrib-s7": "3.1.1"
        }
    }
]

ThingsBoard Dashboard

JSON
Copy paste the JASON code in note pad.Improt that file in the Thingsboard to get the same dashboard in your project.
{
  "title": "Demo",
  "image": null,
  "mobileHide": false,
  "mobileOrder": null,
  "configuration": {
    "description": "",
    "widgets": {
      "754c01cf-44b2-6de8-7ad1-9d3017761a06": {
        "typeFullFqn": "system.cards.value_card",
        "type": "latest",
        "sizeX": 3,
        "sizeY": 3,
        "config": {
          "datasources": [
            {
              "type": "device",
              "name": "",
              "deviceId": "1806f930-2bf8-11f1-87fb-336ff60b6b9e",
              "dataKeys": [
                {
                  "name": "temperature",
                  "type": "timeseries",
                  "label": "Temperature",
                  "color": "#2196f3",
                  "settings": {},
                  "aggregationType": "NONE",
                  "units": null,
                  "decimals": null,
                  "funcBody": null,
                  "usePostProcessing": null,
                  "postFuncBody": null
                }
              ],
              "alarmFilterConfig": {
                "statusList": [
                  "ACTIVE"
                ]
              }
            }
          ],
          "showTitle": false,
          "backgroundColor": "rgba(0, 0, 0, 0)",
          "color": "rgba(0, 0, 0, 0.87)",
          "padding": "0px",
          "settings": {
            "labelPosition": "top",
            "layout": "square",
            "showLabel": true,
            "labelFont": {
              "family": "Roboto",
              "size": 16,
              "sizeUnit": "px",
              "style": "normal",
              "weight": "500"
            },
            "labelColor": {
              "type": "constant",
              "color": "rgba(0, 0, 0, 0.87)",
              "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n  var percent = (temperature + 60)/120 * 100;\n  return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';"
            },
            "showIcon": true,
            "iconSize": 40,
            "iconSizeUnit": "px",
            "icon": "thermostat",
            "iconColor": {
              "type": "constant",
              "color": "#5469FF",
              "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n  var percent = (temperature + 60)/120 * 100;\n  return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';"
            },
            "valueFont": {
              "family": "Roboto",
              "size": 52,
              "sizeUnit": "px",
              "style": "normal",
              "weight": "500"
            },
            "valueColor": {
              "type": "constant",
              "color": "rgba(0, 0, 0, 0.87)",
              "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n  var percent = (temperature + 60)/120 * 100;\n  return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';"
            },
            "showDate": true,
            "dateFormat": {
              "format": null,
              "lastUpdateAgo": true,
              "custom": false
            },
            "dateFont": {
              "family": "Roboto",
              "size": 12,
              "sizeUnit": "px",
              "style": "normal",
              "weight": "500"
            },
            "dateColor": {
              "type": "constant",
              "color": "rgba(0, 0, 0, 0.38)",
              "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n  var percent = (temperature + 60)/120 * 100;\n  return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';"
            },
            "background": {
              "type": "color",
              "color": "#fff",
              "overlay": {
                "enabled": false,
                "color": "rgba(255,255,255,0.72)",
                "blur": 3
              }
            },
            "autoScale": true,
            "padding": ""
          },
          "title": "Value card",
          "dropShadow": true,
          "enableFullscreen": false,
          "titleStyle": {
            "fontSize": "16px",
            "fontWeight": 400
          },
          "units": "°C",
          "decimals": 0,
          "showLegend": false,
          "widgetStyle": {},
          "actions": {},
          "configMode": "basic",
          "margin": "0px",
          "borderRadius": "0px",
          "widgetCss": "",
          "pageSize": 1024,
          "noDataDisplayMessage": "",
          "enableDataExport": false,
          "showTitleIcon": false,
          "titleTooltip": "",
          "titleFont": {
            "size": 12,
            "sizeUnit": "px",
            "family": null,
            "weight": null,
            "style": null,
            "lineHeight": "1.6"
          },
          "titleIcon": "",
          "iconColor": "rgba(0, 0, 0, 0.87)",
          "iconSize": "14px",
          "timewindowStyle": {
            "showIcon": true,
            "iconSize": "14px",
            "icon": "query_builder",
            "iconPosition": "left",
            "font": {
              "size": 12,
              "sizeUnit": "px",
              "family": null,
              "weight": null,
              "style": null,
              "lineHeight": "1"
            },
            "color": null
          }
        },
        "row": 0,
        "col": 0,
        "id": "754c01cf-44b2-6de8-7ad1-9d3017761a06"
      },
      "681c5c73-e119-aca2-7612-2607d514fc63": {
        "typeFullFqn": "system.power_button",
        "type": "rpc",
        "sizeX": 3.5,
        "sizeY": 3.5,
        "config": {
          "showTitle": true,
          "backgroundColor": "#ffffff",
          "color": "rgba(0, 0, 0, 0.87)",
          "padding": "0px",
          "settings": {
            "initialState": {
              "action": "EXECUTE_RPC",
              "defaultValue": false,
              "executeRpc": {
                "method": "getState",
                "requestTimeout": 5000,
                "requestPersistent": false,
                "persistentPollingInterval": 1000
              },
              "getAttribute": {
                "key": "state",
                "scope": null
              },
              "getTimeSeries": {
                "key": "state"
              },
              "getAlarmStatus": {
                "severityList": null,
                "typeList": null
              },
              "dataToValue": {
                "type": "NONE",
                "compareToValue": true,
                "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
              }
            },
            "onUpdateState": {
              "action": "EXECUTE_RPC",
              "executeRpc": {
                "method": "setState",
                "requestTimeout": 5000,
                "requestPersistent": false,
                "persistentPollingInterval": 1000
              },
              "setAttribute": {
                "key": "state",
                "scope": "SERVER_SCOPE"
              },
              "putTimeSeries": {
                "key": "state"
              },
              "valueToData": {
                "type": "CONSTANT",
                "constantValue": true,
                "valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;"
              }
            },
            "offUpdateState": {
              "action": "EXECUTE_RPC",
              "executeRpc": {
                "method": "setState",
                "requestTimeout": 5000,
                "requestPersistent": false,
                "persistentPollingInterval": 1000
              },
              "setAttribute": {
                "key": "state",
                "scope": "SERVER_SCOPE"
              },
              "putTimeSeries": {
                "key": "state"
              },
              "valueToData": {
                "type": "CONSTANT",
                "constantValue": false,
                "valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */ \n return value;"
              }
            },
            "disabledState": {
              "action": "DO_NOTHING",
              "defaultValue": false,
              "getAttribute": {
                "key": "state",
                "scope": null
              },
              "getTimeSeries": {
                "key": "state"
              },
              "getAlarmStatus": {
                "severityList": null,
                "typeList": null
              },
              "dataToValue": {
                "type": "NONE",
                "compareToValue": true,
                "dataToValueFunction": "/* Should return boolean value */\nreturn data;"
              }
            },
            "layout": "default",
            "onButtonIcon": {
              "showIcon": false,
              "iconSize": 32,
              "iconSizeUnit": "px",
              "icon": "power_settings_new"
            },
            "offButtonIcon": {
              "showIcon": false,
              "iconSize": 32,
              "iconSizeUnit": "px",
              "icon": "power_settings_new"
            },
            "mainColorOn": "var(--tb-primary-500)",
            "backgroundColorOn": "#FFFFFF",
            "mainColorOff": "#A2A2A2",
            "backgroundColorOff": "#FFFFFF",
            "mainColorDisabled": "rgba(0,0,0,0.12)",
            "backgroundColorDisabled": "#FFFFFF",
            "background": {
              "type": "color",
              "color": "#fff",
              "overlay": {
                "enabled": false,
                "color": "rgba(255,255,255,0.72)",
                "blur": 3
              }
            },
            "padding": "12px"
          },
          "title": "Power",
          "dropShadow": true,
          "enableFullscreen": false,
          "widgetStyle": {},
          "actions": {},
          "widgetCss": "",
          "noDataDisplayMessage": "",
          "titleFont": {
            "size": 16,
            "sizeUnit": "px",
            "family": "Roboto",
            "weight": "500",
            "style": null,
            "lineHeight": "1.6"
          },
          "showTitleIcon": false,
          "titleTooltip": "",
          "titleStyle": {
            "fontSize": "16px",
            "fontWeight": 400
          },
          "pageSize": 1024,
          "titleIcon": "mdi:lightbulb-outline",
          "iconColor": "rgba(0, 0, 0, 0.87)",
          "iconSize": "24px",
          "configMode": "basic",
          "targetDevice": {
            "type": "device",
            "deviceId": "1806f930-2bf8-11f1-87fb-336ff60b6b9e"
          },
          "titleColor": null,
          "borderRadius": null,
          "datasources": []
        },
        "row": 0,
        "col": 0,
        "id": "681c5c73-e119-aca2-7612-2607d514fc63"
      }
    },
    "states": {
      "default": {
        "name": "Demo",
        "root": true,
        "layouts": {
          "main": {
            "widgets": {
              "754c01cf-44b2-6de8-7ad1-9d3017761a06": {
                "sizeX": 3,
                "sizeY": 3,
                "row": 0,
                "col": 0
              },
              "681c5c73-e119-aca2-7612-2607d514fc63": {
                "sizeX": 3,
                "sizeY": 3,
                "row": 3,
                "col": 0
              }
            },
            "gridSettings": {
              "layoutType": "default",
              "backgroundColor": "#eeeeee",
              "columns": 24,
              "margin": 10,
              "outerMargin": true,
              "backgroundSizeMode": "100%"
            }
          }
        }
      }
    },
    "entityAliases": {},
    "filters": {},
    "timewindow": {
      "selectedTab": 0,
      "realtime": {
        "realtimeType": 0,
        "timewindowMs": 1000
      },
      "aggregation": {
        "type": "NONE",
        "limit": 50000
      }
    },
    "settings": {
      "stateControllerId": "entity",
      "showTitle": false,
      "showDashboardsSelect": true,
      "showEntitiesSelect": true,
      "showDashboardTimewindow": true,
      "showDashboardExport": true,
      "toolbarAlwaysOpen": true
    }
  },
  "name": "Demo",
  "resources": []
}

Credits

Parshwa Vinod Patil
1 project • 0 followers

Comments