871 lines
24 KiB
JSON
871 lines
24 KiB
JSON
|
|
[
|
||
|
|
{
|
||
|
|
"id": "cse_tab",
|
||
|
|
"type": "tab",
|
||
|
|
"label": "CoreSync FROST + Influx + Grafana",
|
||
|
|
"disabled": false,
|
||
|
|
"info": "Measurement and rotatingMachine telemetry are written as raw samples to local InfluxDB and as CoreSync-reduced knots to FROST and InfluxDB."
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_note",
|
||
|
|
"type": "comment",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "Set FROST_USER and FROST_PASSWORD in the Node-RED environment before deploying.",
|
||
|
|
"info": "CoreSync emits FROST-ready HTTP requests. The HTTP request response is fed back with topic=frost.response so metadata lookup/create can continue.",
|
||
|
|
"x": 450,
|
||
|
|
"y": 40,
|
||
|
|
"wires": []
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_ui_base",
|
||
|
|
"type": "ui-base",
|
||
|
|
"name": "EVOLV CoreSync",
|
||
|
|
"path": "/dashboard",
|
||
|
|
"appIcon": "",
|
||
|
|
"includeClientData": true,
|
||
|
|
"acceptsClientConfig": [
|
||
|
|
"ui-notification",
|
||
|
|
"ui-control"
|
||
|
|
],
|
||
|
|
"showPathInSidebar": false,
|
||
|
|
"headerContent": "page",
|
||
|
|
"navigationStyle": "default",
|
||
|
|
"titleBarStyle": "default"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_ui_theme",
|
||
|
|
"type": "ui-theme",
|
||
|
|
"name": "CoreSync Theme",
|
||
|
|
"colors": {
|
||
|
|
"surface": "#ffffff",
|
||
|
|
"primary": "#2364aa",
|
||
|
|
"bgPage": "#f3f5f7",
|
||
|
|
"groupBg": "#ffffff",
|
||
|
|
"groupOutline": "#d5dce3"
|
||
|
|
},
|
||
|
|
"sizes": {
|
||
|
|
"density": "compact",
|
||
|
|
"pagePadding": "12px",
|
||
|
|
"groupGap": "12px",
|
||
|
|
"groupBorderRadius": "6px",
|
||
|
|
"widgetGap": "8px"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_ui_page",
|
||
|
|
"type": "ui-page",
|
||
|
|
"name": "CoreSync FROST",
|
||
|
|
"ui": "cse_ui_base",
|
||
|
|
"path": "/coresync-frost",
|
||
|
|
"icon": "timeline",
|
||
|
|
"layout": "grid",
|
||
|
|
"theme": "cse_ui_theme",
|
||
|
|
"breakpoints": [
|
||
|
|
{
|
||
|
|
"name": "Default",
|
||
|
|
"px": "0",
|
||
|
|
"cols": "12"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"order": 1,
|
||
|
|
"className": ""
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_ui_group_kpi",
|
||
|
|
"type": "ui-group",
|
||
|
|
"name": "Reduction Counters",
|
||
|
|
"page": "cse_ui_page",
|
||
|
|
"width": "12",
|
||
|
|
"height": "1",
|
||
|
|
"order": 1,
|
||
|
|
"showTitle": true,
|
||
|
|
"className": ""
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_ui_group_chart",
|
||
|
|
"type": "ui-group",
|
||
|
|
"name": "Write Rates",
|
||
|
|
"page": "cse_ui_page",
|
||
|
|
"width": "12",
|
||
|
|
"height": "1",
|
||
|
|
"order": 2,
|
||
|
|
"showTitle": true,
|
||
|
|
"className": ""
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_measure_flow",
|
||
|
|
"type": "measurement",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "FROST Flow Sensor FT-101",
|
||
|
|
"scaling": false,
|
||
|
|
"i_min": 0,
|
||
|
|
"i_max": 120,
|
||
|
|
"i_offset": 0,
|
||
|
|
"o_min": 0,
|
||
|
|
"o_max": 120,
|
||
|
|
"simulator": false,
|
||
|
|
"smooth_method": "none",
|
||
|
|
"count": 1,
|
||
|
|
"processOutputFormat": "process",
|
||
|
|
"dbaseOutputFormat": "frost",
|
||
|
|
"uuid": "",
|
||
|
|
"assetTagCode": "FT-101",
|
||
|
|
"tagCode": "FT-101",
|
||
|
|
"supplier": "demo",
|
||
|
|
"category": "sensor",
|
||
|
|
"assetType": "flow",
|
||
|
|
"model": "demo-flow-transmitter",
|
||
|
|
"unit": "m3/h",
|
||
|
|
"enableLog": false,
|
||
|
|
"logLevel": "error",
|
||
|
|
"positionVsParent": "upstream",
|
||
|
|
"positionIcon": "",
|
||
|
|
"hasDistance": false,
|
||
|
|
"distance": 0,
|
||
|
|
"distanceUnit": "m",
|
||
|
|
"distanceDescription": "",
|
||
|
|
"x": 560,
|
||
|
|
"y": 160,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_dbg_measure_process"
|
||
|
|
],
|
||
|
|
[
|
||
|
|
"cse_fn_measure_to_coresync",
|
||
|
|
"cse_fn_raw_influx"
|
||
|
|
],
|
||
|
|
[
|
||
|
|
"cse_rm_pump"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_inj_measure_loop",
|
||
|
|
"type": "inject",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "1 Hz directional flow",
|
||
|
|
"props": [
|
||
|
|
{
|
||
|
|
"p": "payload"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"repeat": "1",
|
||
|
|
"crontab": "",
|
||
|
|
"once": true,
|
||
|
|
"onceDelay": "1",
|
||
|
|
"topic": "",
|
||
|
|
"payload": "",
|
||
|
|
"payloadType": "date",
|
||
|
|
"x": 160,
|
||
|
|
"y": 160,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_fn_measure_triangle"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_fn_measure_triangle",
|
||
|
|
"type": "function",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "triangle 0..100..0",
|
||
|
|
"func": "let i = context.get('i') || 0;\nconst phase = i % 80;\nconst value = phase <= 40 ? phase * 2.5 : (80 - phase) * 2.5;\ncontext.set('i', i + 1);\nmsg.topic = 'measurement';\nmsg.payload = Math.round(value * 100) / 100;\nreturn msg;",
|
||
|
|
"outputs": 1,
|
||
|
|
"noerr": 0,
|
||
|
|
"x": 360,
|
||
|
|
"y": 160,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_measure_flow"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_fn_measure_to_coresync",
|
||
|
|
"type": "function",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "mAbs -> flow.measured.upstream.FT-101",
|
||
|
|
"func": "const p = msg.payload;\nif (!p || !p.fields) return null;\nconst value = Number(p.fields.mAbs);\nif (!Number.isFinite(value)) return null;\nmsg.payload = {\n measurement: 'FT-101',\n fields: { 'flow.measured.upstream.FT-101': value },\n tags: { ...(p.tags || {}), tagcode: 'P-101', sensorTag: 'FT-101', unit: 'm3/h' },\n timestamp: p.timestamp || new Date().toISOString(),\n source: { ...(p.source || {}), softwareType: 'measurement', unit: 'm3/h' }\n};\nmsg.topic = msg.payload.measurement;\nreturn msg;",
|
||
|
|
"outputs": 1,
|
||
|
|
"noerr": 0,
|
||
|
|
"x": 850,
|
||
|
|
"y": 140,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_coresync"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_rm_pump",
|
||
|
|
"type": "rotatingMachine",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "FROST Pump P-101",
|
||
|
|
"speed": "25",
|
||
|
|
"startup": "1",
|
||
|
|
"warmup": "1",
|
||
|
|
"shutdown": "1",
|
||
|
|
"cooldown": "1",
|
||
|
|
"movementMode": "staticspeed",
|
||
|
|
"machineCurve": "",
|
||
|
|
"processOutputFormat": "process",
|
||
|
|
"dbaseOutputFormat": "frost",
|
||
|
|
"uuid": "",
|
||
|
|
"assetTagCode": "P-101",
|
||
|
|
"assetTagNumber": "P-101",
|
||
|
|
"supplier": "",
|
||
|
|
"category": "",
|
||
|
|
"assetType": "",
|
||
|
|
"model": "hidrostal-H05K-S03R",
|
||
|
|
"unit": "m3/h",
|
||
|
|
"enableLog": false,
|
||
|
|
"logLevel": "error",
|
||
|
|
"positionVsParent": "atEquipment",
|
||
|
|
"positionIcon": "",
|
||
|
|
"hasDistance": false,
|
||
|
|
"distance": "",
|
||
|
|
"distanceUnit": "m",
|
||
|
|
"distanceDescription": "",
|
||
|
|
"x": 560,
|
||
|
|
"y": 360,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_dbg_rm_process"
|
||
|
|
],
|
||
|
|
[
|
||
|
|
"cse_coresync",
|
||
|
|
"cse_fn_raw_influx"
|
||
|
|
],
|
||
|
|
[
|
||
|
|
"cse_dbg_rm_parent"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_inj_rm_mode",
|
||
|
|
"type": "inject",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "RM mode virtualControl",
|
||
|
|
"props": [
|
||
|
|
{
|
||
|
|
"p": "topic",
|
||
|
|
"vt": "str"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"p": "payload",
|
||
|
|
"vt": "str"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"repeat": "",
|
||
|
|
"crontab": "",
|
||
|
|
"once": true,
|
||
|
|
"onceDelay": "0.5",
|
||
|
|
"topic": "setMode",
|
||
|
|
"payload": "virtualControl",
|
||
|
|
"payloadType": "str",
|
||
|
|
"x": 180,
|
||
|
|
"y": 280,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_rm_pump"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_inj_rm_start",
|
||
|
|
"type": "inject",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "RM startup",
|
||
|
|
"props": [
|
||
|
|
{
|
||
|
|
"p": "payload",
|
||
|
|
"vt": "str"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"repeat": "",
|
||
|
|
"crontab": "",
|
||
|
|
"once": true,
|
||
|
|
"onceDelay": "1.5",
|
||
|
|
"topic": "",
|
||
|
|
"payload": "startup",
|
||
|
|
"payloadType": "str",
|
||
|
|
"x": 150,
|
||
|
|
"y": 320,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_fn_rm_sequence"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_fn_rm_sequence",
|
||
|
|
"type": "function",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "execSequence",
|
||
|
|
"func": "msg.topic = 'execSequence';\nmsg.payload = { source: 'GUI', action: 'execSequence', parameter: msg.payload };\nreturn msg;",
|
||
|
|
"outputs": 1,
|
||
|
|
"noerr": 0,
|
||
|
|
"x": 340,
|
||
|
|
"y": 320,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_rm_pump"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_inj_rm_setpoint",
|
||
|
|
"type": "inject",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "RM 5s setpoint wave",
|
||
|
|
"props": [
|
||
|
|
{
|
||
|
|
"p": "payload"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"repeat": "5",
|
||
|
|
"crontab": "",
|
||
|
|
"once": true,
|
||
|
|
"onceDelay": "3",
|
||
|
|
"topic": "",
|
||
|
|
"payload": "",
|
||
|
|
"payloadType": "date",
|
||
|
|
"x": 170,
|
||
|
|
"y": 380,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_fn_rm_setpoint"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_fn_rm_setpoint",
|
||
|
|
"type": "function",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "execMovement wave",
|
||
|
|
"func": "let i = context.get('i') || 0;\nconst values = [25, 55, 85, 60, 35];\nconst setpoint = values[i % values.length];\ncontext.set('i', i + 1);\nmsg.topic = 'execMovement';\nmsg.payload = { source: 'GUI', action: 'execMovement', setpoint };\nreturn msg;",
|
||
|
|
"outputs": 1,
|
||
|
|
"noerr": 0,
|
||
|
|
"x": 360,
|
||
|
|
"y": 380,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_rm_pump"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_inj_rm_pressure",
|
||
|
|
"type": "inject",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "RM pressure cycle",
|
||
|
|
"props": [
|
||
|
|
{
|
||
|
|
"p": "payload"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"repeat": "2",
|
||
|
|
"crontab": "",
|
||
|
|
"once": true,
|
||
|
|
"onceDelay": "1",
|
||
|
|
"topic": "",
|
||
|
|
"payload": "",
|
||
|
|
"payloadType": "date",
|
||
|
|
"x": 160,
|
||
|
|
"y": 440,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_fn_rm_pressure"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_fn_rm_pressure",
|
||
|
|
"type": "function",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "simulate upstream/downstream pressure",
|
||
|
|
"func": "let i = context.get('i') || 0;\nconst up = 900 + ((i % 6) * 10);\nconst down = 1700 + ((i % 6) * 40);\ncontext.set('i', i + 1);\nreturn [\n { topic: 'simulateMeasurement', payload: { type: 'pressure', position: 'upstream', value: up, unit: 'mbar' } },\n { topic: 'simulateMeasurement', payload: { type: 'pressure', position: 'downstream', value: down, unit: 'mbar' } }\n];",
|
||
|
|
"outputs": 2,
|
||
|
|
"noerr": 0,
|
||
|
|
"x": 390,
|
||
|
|
"y": 440,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_rm_pump"
|
||
|
|
],
|
||
|
|
[
|
||
|
|
"cse_rm_pump"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_coresync",
|
||
|
|
"type": "coresync",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "CoreSync to WBD FROST",
|
||
|
|
"frostBaseUrl": "https://sta.wbd-rd.nl/FROST-Server/",
|
||
|
|
"serviceVersion": "v1.1",
|
||
|
|
"dbaseFormat": "frost",
|
||
|
|
"assetTagOverride": "",
|
||
|
|
"sensorTagOverride": "",
|
||
|
|
"comparisonMode": "angle",
|
||
|
|
"angleToleranceDeg": 1,
|
||
|
|
"timeScaleMs": 1000,
|
||
|
|
"maxGapMs": 30000,
|
||
|
|
"minDeltaTimeMs": 0,
|
||
|
|
"minDeltaValue": 0,
|
||
|
|
"burstWindowMs": 10,
|
||
|
|
"maxQueuedObservationsPerStream": 2,
|
||
|
|
"diagnosticsEnabled": false,
|
||
|
|
"x": 1120,
|
||
|
|
"y": 260,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_dbg_coresync_process"
|
||
|
|
],
|
||
|
|
[
|
||
|
|
"cse_fn_frost_auth",
|
||
|
|
"cse_fn_knot_influx"
|
||
|
|
],
|
||
|
|
[
|
||
|
|
"cse_dbg_coresync_parent"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_inj_flush",
|
||
|
|
"type": "inject",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "CoreSync flush 15s",
|
||
|
|
"props": [
|
||
|
|
{
|
||
|
|
"p": "topic",
|
||
|
|
"vt": "str"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"repeat": "15",
|
||
|
|
"crontab": "",
|
||
|
|
"once": false,
|
||
|
|
"onceDelay": "",
|
||
|
|
"topic": "coresync.flush",
|
||
|
|
"x": 860,
|
||
|
|
"y": 300,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_coresync"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_fn_frost_auth",
|
||
|
|
"type": "function",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "FROST basic auth from env",
|
||
|
|
"func": "const user = env.get('FROST_USER');\nconst password = env.get('FROST_PASSWORD');\nif (!user || !password) {\n node.status({ fill: 'red', shape: 'ring', text: 'missing FROST_USER/FROST_PASSWORD' });\n return null;\n}\nmsg.headers = { ...(msg.headers || {}), Authorization: 'Basic ' + Buffer.from(`${user}:${password}`).toString('base64') };\nnode.status({ fill: 'green', shape: 'dot', text: msg.topic });\nreturn msg;",
|
||
|
|
"outputs": 1,
|
||
|
|
"noerr": 0,
|
||
|
|
"x": 1420,
|
||
|
|
"y": 220,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_http_frost"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_http_frost",
|
||
|
|
"type": "http request",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "Send to FROST",
|
||
|
|
"method": "use",
|
||
|
|
"ret": "txt",
|
||
|
|
"paytoqs": false,
|
||
|
|
"url": "",
|
||
|
|
"persist": false,
|
||
|
|
"authType": "",
|
||
|
|
"senderr": false,
|
||
|
|
"x": 1630,
|
||
|
|
"y": 220,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_fn_frost_response"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_fn_frost_response",
|
||
|
|
"type": "function",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "topic=frost.response",
|
||
|
|
"func": "if (typeof msg.payload === 'string') {\n try { msg.payload = JSON.parse(msg.payload); } catch (_) { /* keep raw */ }\n}\nconst metric = (msg._coreSync?.kind === 'observation' && msg.statusCode && msg.statusCode < 400)\n ? { topic: 'frostObservation', payload: 1 }\n : null;\nmsg.topic = 'frost.response';\nreturn [msg, metric];",
|
||
|
|
"outputs": 2,
|
||
|
|
"noerr": 0,
|
||
|
|
"x": 1840,
|
||
|
|
"y": 220,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_coresync",
|
||
|
|
"cse_dbg_frost_response"
|
||
|
|
],
|
||
|
|
[
|
||
|
|
"cse_fn_reduction_metrics"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_fn_raw_influx",
|
||
|
|
"type": "function",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "raw EVOLV -> Influx line protocol",
|
||
|
|
"func": "const p = msg.payload;\nif (!p || !p.measurement || !p.fields) return null;\nconst esc = (s) => String(s).replace(/,/g, '\\\\,').replace(/ /g, '\\\\ ').replace(/=/g, '\\\\=');\nconst escString = (s) => String(s).replace(/\"/g, '\\\\\"');\nconst tags = Object.entries(p.tags || {})\n .filter(([, v]) => v !== undefined && v !== null && v !== '')\n .map(([k, v]) => `${esc(k)}=${esc(v)}`)\n .join(',');\nconst fields = Object.entries(p.fields)\n .filter(([, v]) => v !== undefined && v !== null)\n .map(([k, v]) => {\n const n = Number(v);\n if (typeof v === 'number' && Number.isFinite(v)) return `${esc(k)}=${v}`;\n if (typeof v === 'boolean') return `${esc(k)}=${v}`;\n if (Number.isFinite(n) && String(v).trim() !== '') return `${esc(k)}=${n}`;\n return `${esc(k)}=\"${escString(v)}\"`;\n });\nif (!fields.length) return null;\nconst t = Date.parse(p.timestamp);\nconst ns = `${Number.isFinite(t) ? t : Date.now()}000000`;\nmsg._coreSyncDemo = { rawFields: fields.length };\nmsg.payload = `${esc(p.measurement)}${tags ? ',' + tags : ''} ${fields.join(',')} ${ns}`;\nmsg.headers = { Authorization: 'Token evolv-dev-token', 'Content-Type': 'text/plain' };\nmsg.url = 'http://influxdb:8086/api/v2/write?org=evolv&bucket=telemetry&precision=ns';\nmsg.method = 'POST';\nreturn msg;",
|
||
|
|
"outputs": 1,
|
||
|
|
"noerr": 0,
|
||
|
|
"x": 880,
|
||
|
|
"y": 560,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_http_influx_raw"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_http_influx_raw",
|
||
|
|
"type": "http request",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "Write raw InfluxDB",
|
||
|
|
"method": "use",
|
||
|
|
"ret": "txt",
|
||
|
|
"paytoqs": false,
|
||
|
|
"url": "",
|
||
|
|
"persist": false,
|
||
|
|
"authType": "",
|
||
|
|
"senderr": false,
|
||
|
|
"x": 1140,
|
||
|
|
"y": 560,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_fn_influx_raw_status"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_fn_knot_influx",
|
||
|
|
"type": "function",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "CoreSync knot -> Influx line protocol",
|
||
|
|
"func": "if (msg.topic !== 'frost.observation.create' || !msg.payload) return null;\nconst obs = msg.payload;\nconst params = obs.parameters || {};\nconst streamKey = params.evolvStreamKey || msg._coreSync?.streamKey || 'unknown';\nconst parts = streamKey.split(':');\nconst esc = (s) => String(s).replace(/,/g, '\\\\,').replace(/ /g, '\\\\ ').replace(/=/g, '\\\\=');\nconst tags = {\n streamKey,\n thing: parts[0] || 'unknown',\n type: parts[1] || 'unknown',\n variant: parts[2] || 'unknown',\n position: parts[3] || 'unknown',\n sensorTag: parts[4] || 'unknown',\n reason: params.reductionReason || 'unknown',\n direction: params.direction || 'unknown'\n};\nconst tagText = Object.entries(tags).map(([k, v]) => `${esc(k)}=${esc(v)}`).join(',');\nconst fields = [`result=${Number(obs.result)}`, 'knot=1i'];\nif (Number.isFinite(Number(params.slope))) fields.push(`slope=${Number(params.slope)}`);\nif (Number.isFinite(Number(params.previousValue))) fields.push(`previousValue=${Number(params.previousValue)}`);\nconst t = Date.parse(obs.phenomenonTime);\nconst ns = `${Number.isFinite(t) ? t : Date.now()}000000`;\nmsg._coreSyncDemo = { knotFields: 1 };\nmsg.payload = `coresync_knots,${tagText} ${fields.join(',')} ${ns}`;\nmsg.headers = { Authorization: 'Token evolv-dev-token', 'Content-Type': 'text/plain' };\nmsg.url = 'http://influxdb:8086/api/v2/write?org=evolv&bucket=telemetry&precision=ns';\nmsg.method = 'POST';\nreturn msg;",
|
||
|
|
"outputs": 1,
|
||
|
|
"noerr": 0,
|
||
|
|
"x": 1430,
|
||
|
|
"y": 300,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_http_influx_knot"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_http_influx_knot",
|
||
|
|
"type": "http request",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "Write knot InfluxDB",
|
||
|
|
"method": "use",
|
||
|
|
"ret": "txt",
|
||
|
|
"paytoqs": false,
|
||
|
|
"url": "",
|
||
|
|
"persist": false,
|
||
|
|
"authType": "",
|
||
|
|
"senderr": false,
|
||
|
|
"x": 1680,
|
||
|
|
"y": 300,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_fn_influx_knot_status"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_fn_influx_raw_status",
|
||
|
|
"type": "function",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "raw write status",
|
||
|
|
"func": "const count = (context.get('count') || 0) + 1;\nconst errors = context.get('errors') || 0;\nif (msg.statusCode && msg.statusCode >= 400) {\n context.set('errors', errors + 1);\n node.status({ fill: 'red', shape: 'ring', text: `raw ERR ${errors + 1}/${count}` });\n context.set('count', count);\n return null;\n}\nnode.status({ fill: 'green', shape: 'dot', text: `raw ${count} writes` });\ncontext.set('count', count);\nreturn { topic: 'rawFields', payload: msg._coreSyncDemo?.rawFields || 1 };",
|
||
|
|
"outputs": 1,
|
||
|
|
"noerr": 0,
|
||
|
|
"x": 1360,
|
||
|
|
"y": 560,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_fn_reduction_metrics"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_fn_influx_knot_status",
|
||
|
|
"type": "function",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "knot write status",
|
||
|
|
"func": "const count = (context.get('count') || 0) + 1;\nconst errors = context.get('errors') || 0;\nif (msg.statusCode && msg.statusCode >= 400) {\n context.set('errors', errors + 1);\n node.status({ fill: 'red', shape: 'ring', text: `knot ERR ${errors + 1}/${count}` });\n context.set('count', count);\n return null;\n}\nnode.status({ fill: 'green', shape: 'dot', text: `knot ${count} writes` });\ncontext.set('count', count);\nreturn { topic: 'knotFields', payload: msg._coreSyncDemo?.knotFields || 1 };",
|
||
|
|
"outputs": 1,
|
||
|
|
"noerr": 0,
|
||
|
|
"x": 1900,
|
||
|
|
"y": 300,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_fn_reduction_metrics"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_fn_reduction_metrics",
|
||
|
|
"type": "function",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "Live reduction metrics",
|
||
|
|
"func": "const stats = context.get('stats') || { rawFields: 0, knotFields: 0, frostObservations: 0, started: Date.now() };\nconst n = Number(msg.payload) || 0;\nif (msg.topic === 'rawFields') stats.rawFields += n;\nif (msg.topic === 'knotFields') stats.knotFields += n;\nif (msg.topic === 'frostObservation') stats.frostObservations += n;\ncontext.set('stats', stats);\n\nconst saved = stats.rawFields > 0 ? Math.max(0, 100 * (1 - (stats.knotFields / stats.rawFields))) : 0;\nconst elapsedSec = Math.max(1, (Date.now() - stats.started) / 1000);\nconst rawRate = stats.rawFields / elapsedSec;\nconst knotRate = stats.knotFields / elapsedSec;\n\nnode.status({ fill: 'green', shape: 'dot', text: `${stats.rawFields} raw -> ${stats.knotFields} knots (${saved.toFixed(1)}% saved)` });\n\nreturn [\n { payload: `Raw fields: ${stats.rawFields} | CoreSync knots: ${stats.knotFields} | FROST observations: ${stats.frostObservations} | Saved: ${saved.toFixed(1)}%` },\n { payload: Number(saved.toFixed(1)) },\n { topic: 'raw fields/s', payload: Number(rawRate.toFixed(3)), timestamp: Date.now() },\n { topic: 'knot fields/s', payload: Number(knotRate.toFixed(3)), timestamp: Date.now() },\n { payload: stats.frostObservations }\n];",
|
||
|
|
"outputs": 5,
|
||
|
|
"noerr": 0,
|
||
|
|
"x": 1620,
|
||
|
|
"y": 500,
|
||
|
|
"wires": [
|
||
|
|
[
|
||
|
|
"cse_ui_text_summary"
|
||
|
|
],
|
||
|
|
[
|
||
|
|
"cse_ui_gauge_saved"
|
||
|
|
],
|
||
|
|
[
|
||
|
|
"cse_ui_chart_raw"
|
||
|
|
],
|
||
|
|
[
|
||
|
|
"cse_ui_chart_knot"
|
||
|
|
],
|
||
|
|
[
|
||
|
|
"cse_ui_text_frost"
|
||
|
|
]
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_ui_text_summary",
|
||
|
|
"type": "ui-text",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"group": "cse_ui_group_kpi",
|
||
|
|
"name": "Reduction summary",
|
||
|
|
"label": "Reduction",
|
||
|
|
"order": 1,
|
||
|
|
"width": "8",
|
||
|
|
"height": "1",
|
||
|
|
"format": "{{msg.payload}}",
|
||
|
|
"layout": "row-spread",
|
||
|
|
"style": false,
|
||
|
|
"font": "",
|
||
|
|
"fontSize": "",
|
||
|
|
"color": "",
|
||
|
|
"x": 1890,
|
||
|
|
"y": 460,
|
||
|
|
"wires": []
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_ui_text_frost",
|
||
|
|
"type": "ui-text",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"group": "cse_ui_group_kpi",
|
||
|
|
"name": "FROST observations",
|
||
|
|
"label": "FROST observations",
|
||
|
|
"order": 2,
|
||
|
|
"width": "4",
|
||
|
|
"height": "1",
|
||
|
|
"format": "{{msg.payload}}",
|
||
|
|
"layout": "row-spread",
|
||
|
|
"style": false,
|
||
|
|
"font": "",
|
||
|
|
"fontSize": "",
|
||
|
|
"color": "",
|
||
|
|
"x": 1900,
|
||
|
|
"y": 620,
|
||
|
|
"wires": []
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_ui_gauge_saved",
|
||
|
|
"type": "ui-gauge",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"group": "cse_ui_group_kpi",
|
||
|
|
"name": "Saved gauge",
|
||
|
|
"gtype": "gauge-half",
|
||
|
|
"gstyle": "Rounded",
|
||
|
|
"title": "Data Saved",
|
||
|
|
"units": "%",
|
||
|
|
"prefix": "",
|
||
|
|
"suffix": "%",
|
||
|
|
"min": 0,
|
||
|
|
"max": 100,
|
||
|
|
"segments": [
|
||
|
|
{
|
||
|
|
"color": "#d64545",
|
||
|
|
"from": 0
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"color": "#e8a23a",
|
||
|
|
"from": 40
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"color": "#2f9e44",
|
||
|
|
"from": 70
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"width": "4",
|
||
|
|
"height": "3",
|
||
|
|
"order": 3,
|
||
|
|
"x": 1880,
|
||
|
|
"y": 500,
|
||
|
|
"wires": []
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_ui_chart_raw",
|
||
|
|
"type": "ui-chart",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"group": "cse_ui_group_chart",
|
||
|
|
"name": "Raw write rate",
|
||
|
|
"label": "Raw field write rate",
|
||
|
|
"order": 1,
|
||
|
|
"width": "6",
|
||
|
|
"height": "4",
|
||
|
|
"chartType": "line",
|
||
|
|
"category": "topic",
|
||
|
|
"xAxisLabel": "time",
|
||
|
|
"xAxisType": "time",
|
||
|
|
"yAxisLabel": "fields/s",
|
||
|
|
"removeOlder": "15",
|
||
|
|
"removeOlderUnit": "60",
|
||
|
|
"x": 1890,
|
||
|
|
"y": 540,
|
||
|
|
"wires": [],
|
||
|
|
"showLegend": false,
|
||
|
|
"categoryType": "msg",
|
||
|
|
"xAxisProperty": "",
|
||
|
|
"xAxisPropertyType": "timestamp",
|
||
|
|
"yAxisProperty": "payload",
|
||
|
|
"yAxisPropertyType": "msg",
|
||
|
|
"action": "append",
|
||
|
|
"interpolation": "linear"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_ui_chart_knot",
|
||
|
|
"type": "ui-chart",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"group": "cse_ui_group_chart",
|
||
|
|
"name": "Knot write rate",
|
||
|
|
"label": "CoreSync knot write rate",
|
||
|
|
"order": 2,
|
||
|
|
"width": "6",
|
||
|
|
"height": "4",
|
||
|
|
"chartType": "line",
|
||
|
|
"category": "topic",
|
||
|
|
"xAxisLabel": "time",
|
||
|
|
"xAxisType": "time",
|
||
|
|
"yAxisLabel": "knots/s",
|
||
|
|
"removeOlder": "15",
|
||
|
|
"removeOlderUnit": "60",
|
||
|
|
"x": 1890,
|
||
|
|
"y": 580,
|
||
|
|
"wires": [],
|
||
|
|
"showLegend": false,
|
||
|
|
"categoryType": "msg",
|
||
|
|
"xAxisProperty": "",
|
||
|
|
"xAxisPropertyType": "timestamp",
|
||
|
|
"yAxisProperty": "payload",
|
||
|
|
"yAxisPropertyType": "msg",
|
||
|
|
"action": "append",
|
||
|
|
"interpolation": "linear"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_dbg_measure_process",
|
||
|
|
"type": "debug",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "measurement process",
|
||
|
|
"active": false,
|
||
|
|
"tosidebar": true,
|
||
|
|
"console": false,
|
||
|
|
"tostatus": false,
|
||
|
|
"complete": "true",
|
||
|
|
"targetType": "full",
|
||
|
|
"x": 850,
|
||
|
|
"y": 100,
|
||
|
|
"wires": []
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_dbg_rm_process",
|
||
|
|
"type": "debug",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "rotatingMachine process",
|
||
|
|
"active": false,
|
||
|
|
"tosidebar": true,
|
||
|
|
"console": false,
|
||
|
|
"tostatus": false,
|
||
|
|
"complete": "true",
|
||
|
|
"targetType": "full",
|
||
|
|
"x": 840,
|
||
|
|
"y": 360,
|
||
|
|
"wires": []
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_dbg_rm_parent",
|
||
|
|
"type": "debug",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "rotatingMachine parent",
|
||
|
|
"active": false,
|
||
|
|
"tosidebar": true,
|
||
|
|
"console": false,
|
||
|
|
"tostatus": false,
|
||
|
|
"complete": "true",
|
||
|
|
"targetType": "full",
|
||
|
|
"x": 840,
|
||
|
|
"y": 400,
|
||
|
|
"wires": []
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_dbg_coresync_process",
|
||
|
|
"type": "debug",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "CoreSync diagnostics",
|
||
|
|
"active": true,
|
||
|
|
"tosidebar": true,
|
||
|
|
"console": false,
|
||
|
|
"tostatus": false,
|
||
|
|
"complete": "true",
|
||
|
|
"targetType": "full",
|
||
|
|
"x": 1410,
|
||
|
|
"y": 180,
|
||
|
|
"wires": []
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_dbg_coresync_parent",
|
||
|
|
"type": "debug",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "CoreSync parent",
|
||
|
|
"active": false,
|
||
|
|
"tosidebar": true,
|
||
|
|
"console": false,
|
||
|
|
"tostatus": false,
|
||
|
|
"complete": "true",
|
||
|
|
"targetType": "full",
|
||
|
|
"x": 1400,
|
||
|
|
"y": 340,
|
||
|
|
"wires": []
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "cse_dbg_frost_response",
|
||
|
|
"type": "debug",
|
||
|
|
"z": "cse_tab",
|
||
|
|
"name": "FROST response",
|
||
|
|
"active": false,
|
||
|
|
"tosidebar": true,
|
||
|
|
"console": false,
|
||
|
|
"tostatus": false,
|
||
|
|
"complete": "statusCode",
|
||
|
|
"targetType": "msg",
|
||
|
|
"x": 2070,
|
||
|
|
"y": 220,
|
||
|
|
"wires": []
|
||
|
|
}
|
||
|
|
]
|