@opcua/for-node-red
Version:
The Node-RED node to communicate via OPC UA, powered NodeOPCUA and developed by Sterfive's team
1,705 lines • 109 kB
JSON
[
{
"id": "c4495043459c465d",
"type": "subflow",
"name": "gather & repeat",
"info": "",
"category": "",
"in": [
{
"x": 60,
"y": 80,
"wires": [
{
"id": "e97157a71ff3fc6b"
}
]
}
],
"out": [
{
"x": 380,
"y": 80,
"wires": [
{
"id": "e97157a71ff3fc6b",
"port": 0
}
]
}
],
"env": [],
"meta": {},
"color": "#DDAA99"
},
{
"id": "e97157a71ff3fc6b",
"type": "function",
"z": "c4495043459c465d",
"name": "gather & repeat",
"func": "/**\n * This function node receives a monitored DataValue from OPC UA.\n * - msg.payload: Contains the value to be parsed into a float.\n * - msg.sourceTimestamp: Contains the value's timestamp in UTC ISO string format.\n *\n * It outputs messages to the first output with the following payload structure:\n * {\n * timestamp: new Date(msg.sourceTimestamp),\n * value: String(parseFloat(msg.payload))\n * }\n *\n * OPC UA only sends data when a value changes. To maintain a steady data stream, this node\n * repeats the last received value every second. The first repeated message is sent 1.5 seconds\n * after the initial message, with timestamps incrementing by the 1-second interval.\n *\n * Key constraints:\n * - Timestamps must always progress forward.\n * - Timing is based solely on OPC UA timestamps to avoid clock skew issues.\n * - Maintain only a single active timer to prevent duplicate messages.\n * - Timers are cleared properly upon node redeploy.\n */\n\nconst INTERVAL_MS = 1000; // 1-second interval for repeats\nconst INITIAL_DELAY_MS = 1500; // 1.5-second initial delay\n\n// Initialize context variables\nlet timer = context.get('timer');\nlet lastSentTimestamp = context.get('lastSentTimestamp') || 0;\nlet lastValue = context.get('lastValue');\nlet nextExpectedTimestamp = context.get('nextExpectedTimestamp') || 0;\n\n// Cleanup on node redeploy\nif (!context.get('initialized')) {\n node.on('close', function() {\n if (timer) {\n clearTimeout(timer);\n context.set('timer', null);\n }\n });\n context.set('initialized', true);\n}\n\nfunction sendAndSchedule(value, sourceTime) {\n // Send the message immediately\n node.send({\n payload: {\n timestamp: new Date(sourceTime),\n value: value\n }\n });\n\n // Update context\n context.set('lastSentTimestamp', sourceTime);\n context.set('lastValue', value);\n\n // Calculate the next expected timestamp\n const nextTs = sourceTime + INITIAL_DELAY_MS + INTERVAL_MS;\n context.set('nextExpectedTimestamp', nextTs);\n\n // Schedule the next repetition\n const delayUntilNextRepeat = INITIAL_DELAY_MS;\n timer = setTimeout(() => {\n sendRepeatedValue(value, nextTs, INTERVAL_MS);\n }, delayUntilNextRepeat);\n context.set('timer', timer);\n}\n\nfunction sendRepeatedValue(value, nextTs, interval) {\n // Send the repeated message\n node.send({\n payload: {\n timestamp: new Date(nextTs),\n value: value\n }\n });\n\n // Schedule the next repetition\n const nextNextTs = nextTs + interval;\n context.set('nextExpectedTimestamp', nextNextTs);\n\n timer = setTimeout(() => {\n sendRepeatedValue(value, nextNextTs, interval);\n }, interval);\n context.set('timer', timer);\n}\n\nif (msg.payload !== undefined && msg.sourceTimestamp) {\n // Clear any existing timer\n if (timer) {\n clearTimeout(timer);\n context.set('timer', null);\n }\n\n try {\n let sourceTime = new Date(msg.sourceTimestamp).getTime();\n const value = \"\" + parseFloat(msg.payload);\n\n // Ensure timestamps always go forward\n if (lastSentTimestamp && sourceTime <= lastSentTimestamp) {\n sourceTime = lastSentTimestamp + 1; // Force it to be strictly greater\n }\n\n // Send immediately and schedule the first repeat\n sendAndSchedule(value, sourceTime);\n } catch (e) {\n node.error(\"Error processing message: \" + e.message, msg);\n }\n}",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 220,
"y": 80,
"wires": [
[]
]
},
{
"id": "8914237e0d8e46c7",
"type": "tab",
"label": "Advanced Coffee Machine Example",
"disabled": false,
"info": "",
"env": []
},
{
"id": "a872632f0795dffe",
"type": "group",
"z": "8914237e0d8e46c7",
"style": {
"stroke": "#999999",
"stroke-opacity": "1",
"fill": "none",
"fill-opacity": "1",
"label": true,
"label-position": "nw",
"color": "#a4a4a4"
},
"nodes": [
"5163f0e3c15147e2",
"9ce44b437e2ed4eb",
"17f15759ea0b4678",
"ae2a5d89ca13248d",
"44eeed6c80419b5f",
"7b496ba60d331069",
"1c3d6230a57c852c",
"c172f527ba188866",
"ae3fe0588bcec600",
"83de8924a1f6f2a5",
"807487cd9637d0e1",
"e181ef3ed9e6dc40",
"6c7658b34fce0ac3"
],
"x": 1114,
"y": 119,
"w": 432,
"h": 482
},
{
"id": "23e92d93ed23febc",
"type": "group",
"z": "8914237e0d8e46c7",
"name": "Connection status",
"style": {
"stroke": "#ffC000",
"label": true
},
"nodes": [
"dc5e73eb1991eed3",
"36b2628c69b87d42",
"f79e321ce9b22631",
"46d99d7986a03b57",
"e975667424fe78b7",
"dcf7ab764b7d2921"
],
"x": 194,
"y": 939,
"w": 812,
"h": 242
},
{
"id": "b07d4a44ddb86adf",
"type": "group",
"z": "8914237e0d8e46c7",
"name": "Heater",
"style": {
"label": true
},
"nodes": [
"3e9be0f18116b2c2",
"2b573431a25e870f",
"68e0c3653588a07d",
"d3b1d8b5c05c2b22",
"07bde3d3f85f2a40",
"c369c3262a3b57fa",
"f1f77b6afa4257f0",
"20844959a8ea715c"
],
"x": 194,
"y": 699,
"w": 812,
"h": 222
},
{
"id": "62d148a7e9f5c520",
"type": "group",
"z": "8914237e0d8e46c7",
"name": "Current State",
"style": {
"label": true
},
"nodes": [
"c9717ac11b53672e",
"3a8c7eb7dc515e34",
"798535950531337a"
],
"x": 1054,
"y": 699,
"w": 632,
"h": 82
},
{
"id": "11f19caa7b9760b7",
"type": "group",
"z": "8914237e0d8e46c7",
"name": "Pump",
"style": {
"label": true
},
"nodes": [
"f3ba9efdc12e30ce",
"24ab918428d73e5f",
"b8ba5e020aa48e2a",
"7619f93616149cdc"
],
"x": 1054,
"y": 799,
"w": 632,
"h": 122
},
{
"id": "f250c8ad248b028d",
"type": "group",
"z": "8914237e0d8e46c7",
"name": "Process Variables",
"style": {
"fill": "#ffffbf",
"label": true
},
"nodes": [
"517edf10f355e464",
"d22591b8e78ebe7d",
"29542b83f026fb7b",
"684238fb58a91415"
],
"x": 188,
"y": 53,
"w": 724,
"h": 634
},
{
"id": "ec1ab7ca9e060d7f",
"type": "group",
"z": "8914237e0d8e46c7",
"style": {
"stroke": "#999999",
"stroke-opacity": "1",
"fill": "none",
"fill-opacity": "1",
"label": true,
"label-position": "nw",
"color": "#a4a4a4"
},
"nodes": [
"4cecc03d1a64c091",
"f6b1e7b0df0fa288",
"0c098347de28a15d",
"57622c7f24dacef7",
"237e980896f1a684",
"e3c1066f3260a1cb",
"0597a0ffcdab77cd",
"6008fa16cda462d5",
"1fca2f6f28cc4828",
"2857eb76526322a1",
"ea1957e0718f21fd",
"03aa8523b17003d7"
],
"x": 1054,
"y": 939,
"w": 812,
"h": 242
},
{
"id": "230bb82688833807",
"type": "group",
"z": "8914237e0d8e46c7",
"name": "Pooling Read",
"style": {
"label": true
},
"nodes": [
"d21be93e03b60165",
"1832b372c56dc715",
"5f6eeb0fc5228a66",
"5e50f3a2da48a03b",
"fe845d4c6aa473d0",
"50c33de026727ab2",
"99b27a72cfb58370",
"829ce0410d7c3b05",
"1bea16ead1291025"
],
"x": 194,
"y": 1419,
"w": 1172,
"h": 162
},
{
"id": "54277e0eb0f4eddd",
"type": "group",
"z": "8914237e0d8e46c7",
"name": "Display NAMUR Symbol",
"style": {
"label": true
},
"nodes": [
"44225cb3561717c6",
"61237053c6df429d",
"2456ee93776095bc"
],
"x": 194,
"y": 1599,
"w": 712,
"h": 82
},
{
"id": "665736b8cbeb246d",
"type": "group",
"z": "8914237e0d8e46c7",
"name": "Synopsis",
"style": {
"stroke": "#ffbfbf",
"fill": "#e3f3d3",
"label": true
},
"nodes": [
"2c7b916b70107153",
"eeaa86a8240214df",
"42fea19e7e56afbc",
"072413d285aeb40e",
"f64d872549983d6f",
"143a262a2cbfe451",
"a1492e996fe8302a"
],
"x": 194,
"y": 1719,
"w": 1012,
"h": 162
},
{
"id": "a9f1eb481f74dde3",
"type": "group",
"z": "8914237e0d8e46c7",
"name": "Collect data to CSV",
"style": {
"stroke": "#3f93cf",
"fill": "#bfdbef",
"label": true
},
"nodes": [
"b577d7342c008353",
"529de43bbcc8ddd4",
"73a7a87a437b298c",
"239601af1e96d917",
"ccd602507a4abc68",
"b694d72cbaef5d85",
"af5560e53daca7e6",
"96aded34d2e12af9",
"14863b828843ebb1",
"d95b03beeb91e93e"
],
"x": 204,
"y": 1219,
"w": 1122,
"h": 162
},
{
"id": "517edf10f355e464",
"type": "group",
"z": "8914237e0d8e46c7",
"g": "f250c8ad248b028d",
"name": "Water Tank",
"style": {
"label": true,
"fill": "#bfdbef"
},
"nodes": [
"5ddf1b5952313443",
"a66ec0e0a1360f34",
"9122c1157cbe0b5e",
"d2a8ee6868d23307",
"0f373262c51c849b",
"b4b79354c06cc405"
],
"x": 214,
"y": 79,
"w": 672,
"h": 162
},
{
"id": "d22591b8e78ebe7d",
"type": "group",
"z": "8914237e0d8e46c7",
"g": "f250c8ad248b028d",
"name": "Milk Tank",
"style": {
"label": true
},
"nodes": [
"49367ab61a7d3443",
"bbfc30b4f5fe5cdd",
"ab80657e6899a890",
"c5df8a8624445a8a",
"da3f9ba191429d15"
],
"x": 214,
"y": 259,
"w": 652,
"h": 122
},
{
"id": "29542b83f026fb7b",
"type": "group",
"z": "8914237e0d8e46c7",
"g": "f250c8ad248b028d",
"name": "Coffee Beam Tank",
"style": {
"label": true
},
"nodes": [
"0ab25a3cc9a2fb4c",
"f64ae2163a90f520",
"ffdc07a2a8644ee7",
"4d754c69f2e0b376",
"77fa0ee8e20cbe27"
],
"x": 214,
"y": 399,
"w": 672,
"h": 122
},
{
"id": "684238fb58a91415",
"type": "group",
"z": "8914237e0d8e46c7",
"g": "f250c8ad248b028d",
"name": "Boiler",
"style": {
"label": true
},
"nodes": [
"2cfd5287e52e0fb0",
"400b6980b1a2cd45",
"4f1eb368f3fb45c1",
"528f9a8a184a078a",
"f4abb62d50e829d9",
"3287809947131ac3",
"bc32ee4142fc26ab",
"61068469fd79b1a7"
],
"x": 214,
"y": 539,
"w": 632,
"h": 122
},
{
"id": "5ddf1b5952313443",
"type": "OpcUa-Client2-Monitor",
"z": "8914237e0d8e46c7",
"g": "517edf10f355e464",
"inputs": 0,
"output": 2,
"name": "WaterTankLevel",
"endpoint": "9593045b235c321c",
"subscription": "s0",
"startImmediately": true,
"nodeId": "/di:DeviceSet/ns1:CoffeeMachineA.kitchen:Parameters.sterfive:WaterTankLevel",
"samplingInterval": "500",
"discardOldest": true,
"queueSize": 10,
"x": 320,
"y": 160,
"wires": [
[
"d2a8ee6868d23307",
"b4b79354c06cc405"
]
]
},
{
"id": "5163f0e3c15147e2",
"type": "OpcUa-Client2-Call",
"z": "8914237e0d8e46c7",
"g": "a872632f0795dffe",
"inputs": 1,
"output": 4,
"name": "Make Coffee",
"endpoint": "9593045b235c321c",
"action": "read",
"methodId": "/di:DeviceSet/ns1:CoffeeMachineA.di:MethodSet.sterfive:MakeCoffee",
"objectId": "/di:DeviceSet/ns1:CoffeeMachineA",
"x": 1450,
"y": 220,
"wires": [
[]
]
},
{
"id": "9ce44b437e2ed4eb",
"type": "inject",
"z": "8914237e0d8e46c7",
"g": "a872632f0795dffe",
"name": "Mocha",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{\"RecipeName\": \"Mocha\"}",
"payloadType": "json",
"x": 1230,
"y": 220,
"wires": [
[
"5163f0e3c15147e2"
]
]
},
{
"id": "17f15759ea0b4678",
"type": "OpcUa-Client2-Call",
"z": "8914237e0d8e46c7",
"g": "a872632f0795dffe",
"inputs": 1,
"output": 4,
"name": "FillTank",
"endpoint": "9593045b235c321c",
"action": "read",
"methodId": "/di:DeviceSet/ns1:CoffeeMachineA.di:MethodSet.sterfive:FillTank",
"objectId": "/di:DeviceSet/ns1:CoffeeMachineA",
"x": 1450,
"y": 320,
"wires": [
[]
]
},
{
"id": "ae2a5d89ca13248d",
"type": "inject",
"z": "8914237e0d8e46c7",
"g": "a872632f0795dffe",
"name": "",
"props": [],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"x": 1230,
"y": 320,
"wires": [
[
"17f15759ea0b4678"
]
]
},
{
"id": "2cfd5287e52e0fb0",
"type": "OpcUa-Client2-Monitor",
"z": "8914237e0d8e46c7",
"g": "684238fb58a91415",
"inputs": 0,
"output": 2,
"name": "BoilerTempWater",
"endpoint": "9593045b235c321c",
"subscription": "s2",
"startImmediately": true,
"nodeId": "/di:DeviceSet/ns1:CoffeeMachineA.kitchen:Parameters.kitchen:BoilerTempWater",
"samplingInterval": "100",
"discardOldest": true,
"queueSize": 10,
"x": 320,
"y": 580,
"wires": [
[
"528f9a8a184a078a"
]
]
},
{
"id": "2c7b916b70107153",
"type": "OpcUa-Client2-Explore",
"z": "8914237e0d8e46c7",
"g": "665736b8cbeb246d",
"inputs": 1,
"output": 4,
"name": "",
"endpoint": "9593045b235c321c",
"nodeId": "/di:DeviceSet/ns1:CoffeeMachineA.kitchen:Parameters",
"outputType": "AliasedBrowsePath",
"excludeEmpty": false,
"followOrganizes": true,
"x": 460,
"y": 1800,
"wires": [
[
"eeaa86a8240214df",
"a1492e996fe8302a"
]
]
},
{
"id": "eeaa86a8240214df",
"type": "debug",
"z": "8914237e0d8e46c7",
"g": "665736b8cbeb246d",
"name": "debug 8",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 640,
"y": 1760,
"wires": []
},
{
"id": "42fea19e7e56afbc",
"type": "inject",
"z": "8914237e0d8e46c7",
"g": "665736b8cbeb246d",
"name": "AutoStart",
"props": [],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": "1",
"topic": "",
"x": 300,
"y": 1800,
"wires": [
[
"2c7b916b70107153"
]
]
},
{
"id": "072413d285aeb40e",
"type": "OpcUa-Client2-Monitor",
"z": "8914237e0d8e46c7",
"g": "665736b8cbeb246d",
"inputs": 1,
"output": 2,
"name": "",
"endpoint": "9593045b235c321c",
"subscription": "s0",
"startImmediately": false,
"nodeId": "",
"samplingInterval": 1000,
"discardOldest": true,
"queueSize": 10,
"x": 820,
"y": 1800,
"wires": [
[
"f64d872549983d6f",
"143a262a2cbfe451"
]
]
},
{
"id": "f64d872549983d6f",
"type": "debug",
"z": "8914237e0d8e46c7",
"g": "665736b8cbeb246d",
"name": "debug 11",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 1020,
"y": 1760,
"wires": []
},
{
"id": "49367ab61a7d3443",
"type": "OpcUa-Client2-Monitor",
"z": "8914237e0d8e46c7",
"g": "d22591b8e78ebe7d",
"inputs": 0,
"output": 2,
"name": "Milk Tank Level",
"endpoint": "9593045b235c321c",
"subscription": "s0",
"startImmediately": true,
"nodeId": "/di:DeviceSet/ns1:CoffeeMachineA.kitchen:Parameters.sterfive:MilkTankLevel",
"samplingInterval": "10000",
"discardOldest": true,
"queueSize": 10,
"x": 320,
"y": 300,
"wires": [
[
"c5df8a8624445a8a",
"da3f9ba191429d15"
]
]
},
{
"id": "0ab25a3cc9a2fb4c",
"type": "OpcUa-Client2-Monitor",
"z": "8914237e0d8e46c7",
"g": "29542b83f026fb7b",
"inputs": 0,
"output": 2,
"name": "Coffe Beam Level",
"endpoint": "9593045b235c321c",
"subscription": "s0",
"startImmediately": true,
"nodeId": "/di:DeviceSet/ns1:CoffeeMachineA.kitchen:Parameters.sterfive:CoffeeBeanLevel",
"samplingInterval": "10000",
"discardOldest": true,
"queueSize": 10,
"x": 330,
"y": 440,
"wires": [
[
"4d754c69f2e0b376",
"77fa0ee8e20cbe27"
]
]
},
{
"id": "d2a8ee6868d23307",
"type": "function",
"z": "8914237e0d8e46c7",
"g": "517edf10f355e464",
"name": "round",
"func": "msg.payload = Math.ceil(msg.payload*10)/10\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 530,
"y": 160,
"wires": [
[
"9122c1157cbe0b5e",
"0f373262c51c849b"
]
]
},
{
"id": "528f9a8a184a078a",
"type": "function",
"z": "8914237e0d8e46c7",
"g": "684238fb58a91415",
"name": "round",
"func": "msg.payload = Math.ceil(msg.payload*100)/100\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 530,
"y": 580,
"wires": [
[
"400b6980b1a2cd45"
]
]
},
{
"id": "f4abb62d50e829d9",
"type": "function",
"z": "8914237e0d8e46c7",
"g": "684238fb58a91415",
"name": "round",
"func": "msg.payload = Math.ceil(msg.payload*100)/100\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 530,
"y": 580,
"wires": [
[]
]
},
{
"id": "4d754c69f2e0b376",
"type": "function",
"z": "8914237e0d8e46c7",
"g": "29542b83f026fb7b",
"name": "round",
"func": "msg.payload = Math.ceil(msg.payload*1)/1;\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 530,
"y": 440,
"wires": [
[
"f64ae2163a90f520"
]
]
},
{
"id": "c5df8a8624445a8a",
"type": "function",
"z": "8914237e0d8e46c7",
"g": "d22591b8e78ebe7d",
"name": "round",
"func": "msg.payload = Math.ceil(msg.payload*1)/1\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 530,
"y": 300,
"wires": [
[
"bbfc30b4f5fe5cdd"
]
]
},
{
"id": "3287809947131ac3",
"type": "OpcUa-Client2-Monitor",
"z": "8914237e0d8e46c7",
"g": "684238fb58a91415",
"inputs": 0,
"output": 2,
"name": "BoilerTempWater",
"endpoint": "9593045b235c321c",
"subscription": "s2",
"startImmediately": true,
"nodeId": "/di:DeviceSet/ns1:CoffeeMachineA.kitchen:Parameters.kitchen:BoilerTempWater",
"samplingInterval": "100",
"discardOldest": true,
"queueSize": 10,
"x": 320,
"y": 580,
"wires": [
[]
]
},
{
"id": "bc32ee4142fc26ab",
"type": "OpcUa-Client2-Monitor",
"z": "8914237e0d8e46c7",
"g": "684238fb58a91415",
"inputs": 0,
"output": 2,
"name": "BoilerTempWater",
"endpoint": "9593045b235c321c",
"subscription": "s0",
"startImmediately": true,
"nodeId": "/di:DeviceSet/ns1:CoffeeMachineA.kitchen:Parameters.kitchen:BoilerTempWater",
"samplingInterval": "10000",
"discardOldest": true,
"queueSize": 10,
"x": 320,
"y": 580,
"wires": [
[
"61068469fd79b1a7"
]
]
},
{
"id": "f3ba9efdc12e30ce",
"type": "OpcUa-Client2-Monitor",
"z": "8914237e0d8e46c7",
"g": "11f19caa7b9760b7",
"inputs": 0,
"output": 2,
"name": "PumpStatus",
"endpoint": "9593045b235c321c",
"subscription": "s2",
"startImmediately": true,
"nodeId": "/2:DeviceSet/1:CoffeeMachineA.7:Parameters.17:PumpStatus",
"samplingInterval": "1000",
"discardOldest": true,
"queueSize": 10,
"x": 1150,
"y": 840,
"wires": [
[
"b8ba5e020aa48e2a",
"7619f93616149cdc"
]
]
},
{
"id": "b8ba5e020aa48e2a",
"type": "debug",
"z": "8914237e0d8e46c7",
"g": "11f19caa7b9760b7",
"name": "debug 7",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1380,
"y": 880,
"wires": []
},
{
"id": "3e9be0f18116b2c2",
"type": "OpcUa-Client2-Monitor",
"z": "8914237e0d8e46c7",
"g": "b07d4a44ddb86adf",
"inputs": 0,
"output": 2,
"name": "Heater Status",
"endpoint": "9593045b235c321c",
"subscription": "s2",
"startImmediately": true,
"nodeId": "/di:DeviceSet/ns1:CoffeeMachineA.kitchen:Parameters.sterfive:HeaterStatus",
"samplingInterval": "1000",
"discardOldest": true,
"queueSize": 10,
"x": 290,
"y": 780,
"wires": [
[
"2b573431a25e870f",
"20844959a8ea715c"
]
]
},
{
"id": "2b573431a25e870f",
"type": "debug",
"z": "8914237e0d8e46c7",
"g": "b07d4a44ddb86adf",
"name": "debug 10",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 520,
"y": 760,
"wires": []
},
{
"id": "d3b1d8b5c05c2b22",
"type": "debug",
"z": "8914237e0d8e46c7",
"g": "b07d4a44ddb86adf",
"name": "debug 12",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 840,
"y": 840,
"wires": []
},
{
"id": "07bde3d3f85f2a40",
"type": "inject",
"z": "8914237e0d8e46c7",
"g": "b07d4a44ddb86adf",
"name": "",
"props": [
{
"p": "serverTimestamp",
"v": "",
"vt": "date"
},
{
"p": "sourceTimestamp",
"v": "",
"vt": "date"
},
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "1",
"payloadType": "str",
"x": 310,
"y": 840,
"wires": [
[
"20844959a8ea715c"
]
]
},
{
"id": "c369c3262a3b57fa",
"type": "inject",
"z": "8914237e0d8e46c7",
"g": "b07d4a44ddb86adf",
"name": "",
"props": [
{
"p": "serverTimestamp",
"v": "",
"vt": "date"
},
{
"p": "sourceTimestamp",
"v": "",
"vt": "date"
},
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "0",
"payloadType": "str",
"x": 310,
"y": 880,
"wires": [
[
"20844959a8ea715c"
]
]
},
{
"id": "7619f93616149cdc",
"type": "function",
"z": "8914237e0d8e46c7",
"g": "11f19caa7b9760b7",
"name": "gather & repeat",
"func": "/**\n * This function node receives a monitored DataValue from OPC UA.\n * - msg.payload: Contains the value to be parsed into a float.\n * - msg.sourceTimestamp: Contains the value's timestamp in UTC ISO string format.\n *\n * It outputs messages to the first output with the following payload structure:\n * {\n * timestamp: new Date(msg.sourceTimestamp),\n * value: String(parseFloat(msg.payload))\n * }\n *\n * OPC UA only sends data when a value changes. To maintain a steady data stream, this node\n * repeats the last received value every second. The first repeated message is sent 1.5 seconds\n * after the initial message, with timestamps incrementing by the 1-second interval.\n *\n * Key constraints:\n * - Timestamps must always progress forward.\n * - Timing is based solely on OPC UA timestamps to avoid clock skew issues.\n * - Maintain only a single active timer to prevent duplicate messages.\n * - Timers are cleared properly upon node redeploy.\n */\n\nconst INTERVAL_MS = 1000; // 1-second interval for repeats\nconst INITIAL_DELAY_MS = 1500; // 1.5-second initial delay\n\n// Initialize context variables\nlet timer = context.get('timer');\nlet lastSentTimestamp = context.get('lastSentTimestamp') || 0;\nlet lastValue = context.get('lastValue');\nlet nextExpectedTimestamp = context.get('nextExpectedTimestamp') || 0;\n\n// Cleanup on node redeploy\nif (!context.get('initialized')) {\n node.on('close', function () {\n if (timer) {\n clearTimeout(timer);\n context.set('timer', null);\n }\n });\n context.set('initialized', true);\n}\n\nfunction sendAndSchedule(value, sourceTime) {\n // Send the message immediately\n node.send({\n payload: {\n timestamp: new Date(sourceTime),\n value: value\n }\n });\n\n // Update context\n context.set('lastSentTimestamp', sourceTime);\n context.set('lastValue', value);\n\n // Calculate the next expected timestamp\n const nextTs = sourceTime + INITIAL_DELAY_MS + INTERVAL_MS;\n context.set('nextExpectedTimestamp', nextTs);\n\n // Schedule the next repetition\n const delayUntilNextRepeat = INITIAL_DELAY_MS;\n timer = setTimeout(() => {\n sendRepeatedValue(value, nextTs, INTERVAL_MS);\n }, delayUntilNextRepeat);\n context.set('timer', timer);\n}\n\nfunction sendRepeatedValue(value, nextTs, interval) {\n // Send the repeated message\n node.send({\n payload: {\n timestamp: new Date(nextTs),\n value: value\n }\n });\n\n // Schedule the next repetition\n const nextNextTs = nextTs + interval;\n context.set('nextExpectedTimestamp', nextNextTs);\n\n timer = setTimeout(() => {\n sendRepeatedValue(value, nextNextTs, interval);\n }, interval);\n context.set('timer', timer);\n}\n\nif (msg.payload !== undefined && msg.sourceTimestamp) {\n // Clear any existing timer\n if (timer) {\n clearTimeout(timer);\n context.set('timer', null);\n }\n\n try {\n let sourceTime = new Date(msg.sourceTimestamp).getTime();\n const value = \"\" + parseInt(msg.payload);\n\n // Ensure timestamps always go forward\n if (lastSentTimestamp && sourceTime <= lastSentTimestamp) {\n sourceTime = lastSentTimestamp + 1; // Force it to be strictly greater\n }\n\n // Send immediately and schedule the first repeat\n sendAndSchedule(value, sourceTime);\n } catch (e) {\n node.error(\"Error processing message: \" + e.message, msg);\n }\n}",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "\n",
"finalize": "",
"libs": [],
"x": 1400,
"y": 840,
"wires": [
[
"24ab918428d73e5f"
]
]
},
{
"id": "f1f77b6afa4257f0",
"type": "inject",
"z": "8914237e0d8e46c7",
"g": "b07d4a44ddb86adf",
"name": "Clear ",
"props": [
{
"p": "topic",
"vt": "str"
},
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "[]",
"payload": "[]",
"payloadType": "str",
"x": 690,
"y": 740,
"wires": [
[
"68e0c3653588a07d",
"d3b1d8b5c05c2b22"
]
]
},
{
"id": "c9717ac11b53672e",
"type": "function",
"z": "8914237e0d8e46c7",
"g": "62d148a7e9f5c520",
"name": "enum to string",
"func": "switch(msg.payload) {\n case 0:\n msg.payload = \"Off\";\n break;\n case 1:\n msg.payload = \"Standby\";\n break;\n case 2:\n msg.payload = \"Error\";\n break;\n case 3:\n msg.payload = \"Cleaning\";\n break;\n case 4:\n msg.payload = \"Serving Coffee\";\n break;\n case 5:\n msg.payload = \"Under Maintenance\";\n break;\n default:\n msg.payload = msg.payload;\n break;\n\n}\n// msg.payload = msg.payload;\nreturn msg;\n\n/*\n\nexport enum EnumCoffeeMachineModeEx {\n Off = 0,\n Standby = 1,\n Error = 2,\n Cleaning = 3,\n\n // not in spec !!!\n ServingCoffee = 4,\n\n UnderMaintenance = 5, // like filling coffee\n\n}\n*/",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1360,
"y": 740,
"wires": [
[
"798535950531337a"
]
]
},
{
"id": "3a8c7eb7dc515e34",
"type": "OpcUa-Client2-Monitor",
"z": "8914237e0d8e46c7",
"g": "62d148a7e9f5c520",
"inputs": 0,
"output": 2,
"name": "CurrentState",
"endpoint": "9593045b235c321c",
"subscription": "s2",
"startImmediately": true,
"nodeId": "/di:DeviceSet/ns1:CoffeeMachineA.kitchen:Parameters.kitchen:CurrentState",
"samplingInterval": "100",
"discardOldest": true,
"queueSize": 10,
"x": 1150,
"y": 740,
"wires": [
[
"c9717ac11b53672e"
]
]
},
{
"id": "1c3d6230a57c852c",
"type": "OpcUa-Client2-Call",
"z": "8914237e0d8e46c7",
"g": "a872632f0795dffe",
"inputs": 1,
"output": 4,
"name": "Start",
"endpoint": "9593045b235c321c",
"action": "read",
"methodId": "/di:DeviceSet/ns1:CoffeeMachineA.di:MethodSet.sterfive:Start",
"objectId": "/di:DeviceSet/ns1:CoffeeMachineA",
"x": 1450,
"y": 420,
"wires": [
[]
]
},
{
"id": "c172f527ba188866",
"type": "inject",
"z": "8914237e0d8e46c7",
"g": "a872632f0795dffe",
"name": "",
"props": [],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"x": 1230,
"y": 420,
"wires": [
[
"1c3d6230a57c852c"
]
]
},
{
"id": "83de8924a1f6f2a5",
"type": "OpcUa-Client2-Call",
"z": "8914237e0d8e46c7",
"g": "a872632f0795dffe",
"inputs": 1,
"output": 4,
"name": "Stop",
"endpoint": "9593045b235c321c",
"action": "read",
"methodId": "/di:DeviceSet/ns1:CoffeeMachineA.di:MethodSet.sterfive:Stop",
"objectId": "/di:DeviceSet/ns1:CoffeeMachineA",
"x": 1440,
"y": 520,
"wires": [
[]
]
},
{
"id": "807487cd9637d0e1",
"type": "inject",
"z": "8914237e0d8e46c7",
"g": "a872632f0795dffe",
"name": "",
"props": [],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"x": 1230,
"y": 520,
"wires": [
[
"83de8924a1f6f2a5"
]
]
},
{
"id": "36b2628c69b87d42",
"type": "OpcUa-Client2-Read",
"z": "8914237e0d8e46c7",
"g": "23e92d93ed23febc",
"inputs": 1,
"output": 4,
"name": "",
"endpoint": "9593045b235c321c",
"attributeId": "Value",
"nodeId": "",
"outputType": "StatusCode",
"x": 590,
"y": 1080,
"wires": [
[
"46d99d7986a03b57",
"e975667424fe78b7"
]
]
},
{
"id": "46d99d7986a03b57",
"type": "function",
"z": "8914237e0d8e46c7",
"g": "23e92d93ed23febc",
"name": "Extract connection status",
"func": "msg.payload = `${msg.payload} - ${msg.statusCode.toString()}`;\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 830,
"y": 1080,
"wires": [
[
"dcf7ab764b7d2921"
]
]
},
{
"id": "e975667424fe78b7",
"type": "delay",
"z": "8914237e0d8e46c7",
"g": "23e92d93ed23febc",
"name": "",
"pauseType": "delay",
"timeout": "1",
"timeoutUnits": "seconds",
"rate": "1",
"nbRateUnits": "1",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": false,
"allowrate": false,
"outputs": 1,
"x": 480,
"y": 980,
"wires": [
[
"f79e321ce9b22631"
]
]
},
{
"id": "dc5e73eb1991eed3",
"type": "inject",
"z": "8914237e0d8e46c7",
"g": "23e92d93ed23febc",
"name": "",
"props": [],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": "1",
"topic": "",
"x": 290,
"y": 1100,
"wires": [
[
"36b2628c69b87d42"
]
]
},
{
"id": "f79e321ce9b22631",
"type": "function",
"z": "8914237e0d8e46c7",
"g": "23e92d93ed23febc",
"name": "remove payload",
"func": "msg.payload = null;\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 360,
"y": 1060,
"wires": [
[
"36b2628c69b87d42"
]
]
},
{
"id": "6c7658b34fce0ac3",
"type": "comment",
"z": "8914237e0d8e46c7",
"g": "a872632f0795dffe",
"name": "Buttons",
"info": "",
"x": 1190,
"y": 160,
"wires": []
},
{
"id": "9326ae6929032e87",
"type": "comment",
"z": "8914237e0d8e46c7",
"name": "Coffee Machine ( visit http://127.0.0.1:1880/dashboard/CoffeeMachineSynopsis )",
"info": "# Coffee Machine *\n\nThis example demostrate how to \ncreate a Dashboard that interacts \nwith a OPCUA CoffeeMachine\n\n",
"x": 480,
"y": 40,
"wires": []
},
{
"id": "b577d7342c008353",
"type": "OpcUa-Client2-Explore",
"z": "8914237e0d8e46c7",
"g": "a9f1eb481f74dde3",
"inputs": 1,
"output": 4,
"name": "",
"endpoint": "9593045b235c321c",
"nodeId": "/di:DeviceSet/ns1:CoffeeMachineA.kitchen:Parameters",
"outputType": "Value",
"excludeEmpty": false,
"followOrganizes": false,
"x": 460,
"y": 1260,
"wires": [
[
"af5560e53daca7e6",
"96aded34d2e12af9"
]
]
},
{
"id": "529de43bbcc8ddd4",
"type": "inject",
"z": "8914237e0d8e46c7",
"g": "a9f1eb481f74dde3",
"name": "",
"props": [],
"repeat": "10",
"crontab": "",
"once": false,
"onceDelay": "2",
"topic": "",
"x": 300,
"y": 1260,
"wires": [
[
"b577d7342c008353"
]
]
},
{
"id": "73a7a87a437b298c",
"type": "csv",
"z": "8914237e0d8e46c7",
"g": "a9f1eb481f74dde3",
"name": "",
"spec": "rfc",
"sep": ";",
"hdrin": "",
"hdrout": "once",
"multi": "one",
"ret": "\\n",
"temp": "",
"skip": "0",
"strings": true,
"include_empty_strings": "",
"include_null_values": "",
"x": 990,
"y": 1260,
"wires": [
[
"239601af1e96d917",
"ccd602507a4abc68"
]
]
},
{
"id": "239601af1e96d917",
"type": "file",
"z": "8914237e0d8e46c7",
"g": "a9f1eb481f74dde3",
"name": "Append to Output.csv",
"filename": "/tmp/output.csv",
"filenameType": "str",
"appendNewline": false,
"createDir": false,
"overwriteFile": "false",
"encoding": "none",
"x": 1200,
"y": 1260,
"wires": [
[]
]
},
{
"id": "ccd602507a4abc68",
"type": "debug",
"z": "8914237e0d8e46c7",
"g": "a9f1eb481f74dde3",
"name": "debug 13",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 1160,
"y": 1300,
"wires": []
},
{
"id": "d21be93e03b60165",
"type": "OpcUa-Client2-Explore",
"z": "8914237e0d8e46c7",
"g": "230bb82688833807",
"inputs": 1,
"output": 4,
"name": "",
"endpoint": "9593045b235c321c",
"nodeId": "/di:DeviceSet/ns1:CoffeeMachineA.kitchen:Parameters",
"outputType": "NodeId",
"excludeEmpty": false,
"followOrganizes": false,
"x": 440,
"y": 1540,
"wires": [
[
"50c33de026727ab2"
]
]
},
{
"id": "1832b372c56dc715",
"type": "inject",
"z": "8914237e0d8e46c7",
"g": "230bb82688833807",
"name": "",
"props": [],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "2",
"topic": "",
"x": 290,
"y": 1540,
"wires": [
[
"d21be93e03b60165"
]
]
},
{
"id": "5f6eeb0fc5228a66",
"type": "OpcUa-Client2-Read",
"z": "8914237e0d8e46c7",
"g": "230bb82688833807",
"inputs": 1,
"output": 4,
"name": "",
"endpoint": "9593045b235c321c",
"attributeId": "Value",
"nodeId": "",
"outputType": "Value",
"x": 870,
"y": 1540,
"wires": [
[
"fe845d4c6aa473d0",
"829ce0410d7c3b05",
"5e50f3a2da48a03b"
]
]
},
{
"id": "5e50f3a2da48a03b",
"type": "debug",
"z": "8914237e0d8e46c7",
"g": "230bb82688833807",
"name": "debug 15",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1260,
"y": 1460,
"wires": []
},
{
"id": "fe845d4c6aa473d0",
"type": "delay",
"z": "8914237e0d8e46c7",
"g": "230bb82688833807",
"name": "",
"pauseType": "delay",
"timeout": "2",
"timeoutUnits": "seconds",
"rate": "1",
"nbRateUnits": "1",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": false,
"allowrate": false,
"outputs": 1,
"x": 840,
"y": 1480,
"wires": [
[
"99b27a72cfb58370"
]
]
},
{
"id": "50c33de026727ab2",
"type": "function",
"z": "8914237e0d8e46c7",
"g": "230bb82688833807",
"name": "memorize payload",
"func": "flow.set(\"p\", msg.payload)\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 630,
"y": 1540,
"wires": [
[
"5f6eeb0fc5228a66"
]
]
},
{
"id": "99b27a72cfb58370",
"type": "function",
"z": "8914237e0d8e46c7",
"g": "230bb82688833807",
"name": "function 3",
"func": "msg.payload = flow.get(\"p\");\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 955,
"y": 1480,
"wires": [
[
"5f6eeb0fc5228a66"
]
],
"l": false
},
{
"id": "829ce0410d7c3b05",
"type": "function",
"z": "8914237e0d8e46c7",
"g": "230bb82688833807",
"name": "clean up",
"func": "msg.timestamp = new Date(); // new Date(msg.sourceTimestamp);\nmsg.payload.TotalMix = msg.payload.ServedCoffeeCount;\nmsg.payload.timestamp = msg.timestamp;\nmsg.payload.d= msg.timestamp.toISOString();\nmsg.payload.values = [ Math.random()*1000 , Math.random()*1000];\nmsg.payload.titles = [ \"A\", \"B\"]\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1100,
"y": 1540,
"wires": [
[
"5e50f3a2da48a03b",
"1bea16ead1291025"
]
]
},
{
"id": "4cecc03d1a64c091",
"type": "OpcUa-Client2-Browse",
"z": "8914237e0d8e46c7",
"g": "ec1ab7ca9e060d7f",
"inputs": 1,
"output": 4,
"name": "",
"endpoint": "9593045b235c321c",
"nodeId": "/di:DeviceSet/ns1:CoffeeMachineA.sterfive:Recipes",
"referenceTypeId": "HasChild",
"includeSubtypes": false,
"browseDirection": "Forward",
"nodeClassMask": "Object",
"resultMask": "ReferenceType | BrowseName | TypeDefinition | DisplayName | NodeClass | IsForward",
"x": 1320,
"y": 980,
"wires": [
[
"57622c7f24dacef7"
]
]
},
{
"id": "f6b1e7b0df0fa288",
"type": "debug",
"z": "8914237e0d8e46c7",
"g": "ec1ab7ca9e060d7f",
"name": "debug 16",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1640,
"y": 1040,
"wires": []
},
{
"id": "0c098347de28a15d",
"type": "inject",
"z": "8914237e0d8e46c7",
"g": "ec1ab7ca9e060d7f",
"name": "",
"props": [],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": "1",
"topic": "",
"x": 1150,
"y": 980,
"wires": [
[
"4cecc03d1a64c091"
]
]
},
{
"id": "57622c7f24dacef7",
"type": "function",
"z": "8914237e0d8e46c7",
"g": "ec1ab7ca9e060d7f",
"name": "extract recipe name",
"func": "msg.payload = msg.payload.map(ref => ref.displayName );\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1510,
"y": 980,
"wires": [
[
"e3c1066f3260a1cb"
]
]
},
{
"id": "e3c1066f3260a1cb",
"type": "function",
"z": "8914237e0d8e46c7",
"g": "ec1ab7ca9e060d7f",
"name": "prepare combo box options",
"func": "msg.ui_update = {\n options: msg.payload\n};\nmsg.payload = msg.payload[0].value;\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1240,
"y": 1060,
"wires": [
[
"f6b1e7b0df0fa288",
"237e980896f1a684"
]
]
},
{
"id": "0597a0ffcdab77cd",
"type": "function",
"z": "8914237e0d8e46c7",
"g": "ec1ab7ca9e060d7f",
"name": "memorize selectedComboValue",
"func": "// This function is triggered whenever the combo box value changes.\n// It stores the selected value in the flow context.\nflow.set(\"selectedComboValue\", msg.payload);\nreturn null; // No output required from this node.\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1710