Hardware components | ||||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
![]() |
| |||||
| ||||||
Hand tools and fabrication machines | ||||||
| ||||||
| ||||||
The Challenge
As an engineer at Trimasys Control Solutions, I was recently tasked with developing a robust cloud-based dashboard for real-time monitoring and control of industrial assets. The goal was simple but ambitious: create a bi-directional communication bridge between a physical PLC and a remote cloud interface.
Exploring the Options
In the initial phase, I evaluated several industry-leading platforms, including:
- Grafana: Excellent for visualization but requires more effort for bi-directional "Control."
- AWS IoT Core: Powerful and scalable, but can have a steep learning curve and cost implications for smaller deployments.
- On-site Solutions: Secure, but lacked the "anywhere-access" flexibility the project required.
Why This Path?
After testing various ecosystems, I chose the combination of Siemens S7-1200, Node-RED, and ThingsBoard Cloud. I selected this path because:
- Industrial Grade: It uses the standard S7 Protocol and MQTT, making it reliable for factory environments.
- Beginner Friendly: Node-RED’s low-code environment allows even total freshers to get up and running quickly.
- Cost-Effective: By leveraging Open-Source software like Node-RED and the free tier of ThingsBoard, the barrier to entry is nearly zero.
What This Guide Covers
This article serves as a foundational guide for basic Read/Write operations. Whether you are a student or a professional beginner in the IoT space, this guide will help you:
- Extract live telemetry data from a Siemens PLC.
- Bridge that data to the cloud using MQTT.
- Send Remote Procedure Calls (RPC) back to the PLC to toggle physical outputs.
Note: This is the "Basic Documentation" of my workflow. I will soon be publishing the full-scale industrial project I developed at Trimasys, but mastering these basics is the essential first step!
[
{
"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{
"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": []
}









Comments