updates to rotating machine struct

This commit is contained in:
znetsixe
2026-02-12 10:48:44 +01:00
parent c63701db38
commit 405be33626
15 changed files with 1978 additions and 7 deletions

49
examples/README.md Normal file
View File

@@ -0,0 +1,49 @@
# RotatingMachine Example Flows
These flows are import-ready Node-RED examples focused on the `rotatingMachine` node.
## Files
- `basic.flow.json`
Purpose: quick manual control + local visualization.
Includes:
- mode selection
- startup/shutdown/emergency buttons
- setpoint control
- simulated upstream/downstream pressure input
- local charts for predicted flow/power/ctrl
- `integration.flow.json`
Purpose: richer scenario testing from dashboard controls.
Includes:
- sequence controls (startup/shutdown/maintenance)
- direct setpoint + flowMovement commands
- simulated pressure inputs
- charts for flow, power, NCog%
- state snapshot text
- `edge.flow.json`
Purpose: intentionally send invalid/boundary inputs and observe behavior.
Includes:
- invalid mode command
- negative setpoint command
- disallowed source sequence command
- unsupported simulated measurement type
- debug outputs for process/influx/parent channels
## Requirements
- EVOLV rotatingMachine node installed/available in Node-RED.
- FlowFuse Dashboard 2 nodes installed (`ui-base`, `ui-page`, `ui-group`, etc.).
## Import
1. In Node-RED, use `Import` and select one of the `*.flow.json` files.
2. Deploy.
3. Open the dashboard page path configured in the flow.
## Notes
- These examples are manual by design: no auto-start on deploy.
- Pressure simulation uses `msg.topic = "simulateMeasurement"` handled by the rotatingMachine wrapper.
- Output graphs are based on the rotatingMachine process output payload fields.

644
examples/basic.flow.json Normal file
View File

@@ -0,0 +1,644 @@
[
{
"id": "f1e8a6c8b2a4477f",
"type": "tab",
"label": "RotatingMachine Basic",
"disabled": false,
"info": "Basic manual control and visualization for rotatingMachine"
},
{
"id": "ui_base_rm_basic",
"type": "ui-base",
"name": "EVOLV Demo",
"path": "/dashboard",
"appIcon": "",
"includeClientData": true,
"acceptsClientConfig": [
"ui-notification",
"ui-control"
],
"showPathInSidebar": false,
"headerContent": "page",
"navigationStyle": "default",
"titleBarStyle": "default"
},
{
"id": "ui_theme_rm_basic",
"type": "ui-theme",
"name": "EVOLV Basic Theme",
"colors": {
"surface": "#ffffff",
"primary": "#0094ce",
"bgPage": "#eeeeee",
"groupBg": "#ffffff",
"groupOutline": "#cccccc"
},
"sizes": {
"density": "default",
"pagePadding": "12px",
"groupGap": "12px",
"groupBorderRadius": "4px",
"widgetGap": "12px"
}
},
{
"id": "ui_page_rm_basic",
"type": "ui-page",
"name": "RotatingMachine Basic",
"ui": "ui_base_rm_basic",
"path": "/rotating-machine-basic",
"icon": "settings_input_component",
"layout": "grid",
"theme": "ui_theme_rm_basic",
"breakpoints": [
{
"name": "Default",
"px": "0",
"cols": "12"
}
],
"order": 1,
"className": ""
},
{
"id": "ui_group_rm_ctrl",
"type": "ui-group",
"name": "Machine Controls",
"page": "ui_page_rm_basic",
"width": "6",
"height": "1",
"order": 1,
"showTitle": true,
"className": ""
},
{
"id": "ui_group_rm_sim",
"type": "ui-group",
"name": "Simulation Inputs",
"page": "ui_page_rm_basic",
"width": "6",
"height": "1",
"order": 2,
"showTitle": true,
"className": ""
},
{
"id": "ui_group_rm_obs",
"type": "ui-group",
"name": "Observed Output",
"page": "ui_page_rm_basic",
"width": "12",
"height": "24",
"order": 3,
"showTitle": true,
"className": ""
},
{
"id": "rm_node_basic",
"type": "rotatingMachine",
"z": "f1e8a6c8b2a4477f",
"name": "RM Basic",
"speed": "1",
"startup": "3",
"warmup": "3",
"shutdown": "3",
"cooldown": "3",
"movementMode": "staticspeed",
"machineCurve": "",
"uuid": "",
"supplier": "hidrostal",
"category": "machine",
"assetType": "pump-centrifugal",
"model": "hidrostal-H05K-S03R",
"unit": "m3/h",
"enableLog": false,
"logLevel": "error",
"positionVsParent": "atEquipment",
"positionIcon": "",
"hasDistance": false,
"distance": "",
"distanceUnit": "m",
"distanceDescription": "",
"x": 1060,
"y": 420,
"wires": [
[
"rm_parse_output"
],
[
"rm_debug_influx"
],
[
"rm_debug_parent"
]
]
},
{
"id": "rm_mode_dropdown",
"type": "ui-dropdown",
"z": "f1e8a6c8b2a4477f",
"group": "ui_group_rm_ctrl",
"name": "Mode",
"label": "Mode",
"order": 1,
"width": "6",
"height": "1",
"passthru": true,
"multiple": false,
"options": [
{
"label": "auto",
"value": "auto",
"type": "str"
},
{
"label": "virtualControl",
"value": "virtualControl",
"type": "str"
},
{
"label": "fysicalControl",
"value": "fysicalControl",
"type": "str"
}
],
"payload": "",
"topic": "setMode",
"x": 210,
"y": 160,
"wires": [
[
"rm_set_mode"
]
]
},
{
"id": "rm_set_mode",
"type": "change",
"z": "f1e8a6c8b2a4477f",
"name": "topic=setMode",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "setMode",
"tot": "str"
}
],
"x": 440,
"y": 160,
"wires": [
[
"rm_node_basic"
]
]
},
{
"id": "rm_startup_btn",
"type": "ui-button",
"z": "f1e8a6c8b2a4477f",
"group": "ui_group_rm_ctrl",
"name": "Startup",
"label": "Startup",
"order": 2,
"width": "3",
"height": "1",
"emulateClick": false,
"tooltip": "Run startup sequence",
"color": "",
"bgcolor": "",
"icon": "play_arrow",
"payload": "startup",
"payloadType": "str",
"topic": "",
"x": 200,
"y": 220,
"wires": [
[
"rm_exec_sequence_builder"
]
]
},
{
"id": "rm_shutdown_btn",
"type": "ui-button",
"z": "f1e8a6c8b2a4477f",
"group": "ui_group_rm_ctrl",
"name": "Shutdown",
"label": "Shutdown",
"order": 3,
"width": "3",
"height": "1",
"emulateClick": false,
"tooltip": "Run shutdown sequence",
"icon": "stop",
"payload": "shutdown",
"payloadType": "str",
"topic": "",
"x": 200,
"y": 260,
"wires": [
[
"rm_exec_sequence_builder"
]
]
},
{
"id": "rm_emergency_btn",
"type": "ui-button",
"z": "f1e8a6c8b2a4477f",
"group": "ui_group_rm_ctrl",
"name": "Emergency Stop",
"label": "Emergency Stop",
"order": 4,
"width": "6",
"height": "1",
"emulateClick": false,
"tooltip": "Emergency stop",
"color": "#ffffff",
"bgcolor": "#cc0000",
"icon": "warning",
"payload": "{\"source\":\"GUI\",\"action\":\"emergencystop\"}",
"payloadType": "json",
"topic": "emergencystop",
"x": 240,
"y": 300,
"wires": [
[
"rm_node_basic"
]
]
},
{
"id": "rm_exec_sequence_builder",
"type": "function",
"z": "f1e8a6c8b2a4477f",
"name": "Build execSequence",
"func": "msg.topic = 'execSequence';\nmsg.payload = {\n source: 'GUI',\n action: 'execSequence',\n parameter: msg.payload\n};\nreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 470,
"y": 240,
"wires": [
[
"rm_node_basic"
]
]
},
{
"id": "rm_setpoint_input",
"type": "ui-number-input",
"z": "f1e8a6c8b2a4477f",
"group": "ui_group_rm_ctrl",
"name": "Setpoint",
"label": "Setpoint %",
"order": 5,
"width": "6",
"height": "1",
"passthru": true,
"topic": "",
"min": 0,
"max": 100,
"step": 1,
"x": 200,
"y": 360,
"wires": [
[
"rm_exec_movement_builder",
"rm_ctrl_setpoint_for_chart"
]
]
},
{
"id": "rm_exec_movement_builder",
"type": "function",
"z": "f1e8a6c8b2a4477f",
"name": "Build execMovement",
"func": "msg.topic = 'execMovement';\nmsg.payload = {\n source: 'GUI',\n action: 'execMovement',\n setpoint: Number(msg.payload)\n};\nreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 480,
"y": 360,
"wires": [
[
"rm_node_basic"
]
]
},
{
"id": "rm_pressure_down_input",
"type": "ui-number-input",
"z": "f1e8a6c8b2a4477f",
"group": "ui_group_rm_sim",
"name": "Downstream Pressure",
"label": "Downstream pressure (mbar)",
"order": 1,
"width": "6",
"height": "1",
"passthru": true,
"topic": "",
"min": 0,
"max": 3000,
"step": 10,
"x": 230,
"y": 460,
"wires": [
[
"rm_sim_pressure_down_builder"
]
]
},
{
"id": "rm_pressure_up_input",
"type": "ui-number-input",
"z": "f1e8a6c8b2a4477f",
"group": "ui_group_rm_sim",
"name": "Upstream Pressure",
"label": "Upstream pressure (mbar)",
"order": 2,
"width": "6",
"height": "1",
"passthru": true,
"topic": "",
"min": 0,
"max": 3000,
"step": 10,
"x": 220,
"y": 500,
"wires": [
[
"rm_sim_pressure_up_builder"
]
]
},
{
"id": "rm_sim_pressure_down_builder",
"type": "function",
"z": "f1e8a6c8b2a4477f",
"name": "simulate downstream pressure",
"func": "msg.topic = 'simulateMeasurement';\nmsg.payload = {\n type: 'pressure',\n position: 'downstream',\n value: Number(msg.payload),\n unit: 'mbar'\n};\nreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 510,
"y": 460,
"wires": [
[
"rm_node_basic"
]
]
},
{
"id": "rm_sim_pressure_up_builder",
"type": "function",
"z": "f1e8a6c8b2a4477f",
"name": "simulate upstream pressure",
"func": "msg.topic = 'simulateMeasurement';\nmsg.payload = {\n type: 'pressure',\n position: 'upstream',\n value: Number(msg.payload),\n unit: 'mbar'\n};\nreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 500,
"y": 500,
"wires": [
[
"rm_node_basic"
]
]
},
{
"id": "rm_parse_output",
"type": "function",
"z": "f1e8a6c8b2a4477f",
"name": "Parse RM process output",
"func": "const incoming = (msg && msg.payload && typeof msg.payload === 'object') ? msg.payload : {};\nconst lastPayload = context.get('lastPayload') || {};\nconst merged = { ...lastPayload, ...incoming };\ncontext.set('lastPayload', merged);\n\nconst cache = context.get('metricCache') || {\n flow: 0,\n power: 0,\n ctrl: 0,\n nCog: 0,\n stateCode: 0,\n state: 'idle',\n mode: 'auto',\n runtime: 0,\n moveTimeleft: 0,\n maintenanceTime: 0\n};\n\nconst pickNumber = (...keys) => {\n for (const key of keys) {\n const value = Number(merged[key]);\n if (Number.isFinite(value)) return value;\n }\n return null;\n};\n\nconst pickString = (key, fallback = null) => {\n const value = merged[key];\n if (value === undefined || value === null || value === '') return fallback;\n return String(value);\n};\n\nconst flow = pickNumber('flow.predicted.downstream.default', 'flow.predicted.downstream');\nconst power = pickNumber('power.predicted.atequipment.default', 'power.predicted.atequipment', 'power.predicted.atEquipment.default', 'power.predicted.atEquipment');\nconst ctrl = pickNumber('ctrl', 'ctrl.predicted.atequipment.default', 'ctrl.predicted.atequipment', 'ctrl.predicted.atEquipment.default', 'ctrl.predicted.atEquipment');\nconst nCog = pickNumber('NCogPercent', 'NCog');\nconst runtime = pickNumber('runtime');\nconst moveTimeleft = pickNumber('moveTimeleft');\nconst maintenanceTime = pickNumber('maintenanceTime');\nconst state = pickString('state', cache.state);\nconst mode = pickString('mode', cache.mode);\n\nconst stateCodeMap = { off: 0, idle: 1, starting: 2, warmingup: 3, operational: 4, accelerating: 5, decelerating: 6, stopping: 7, coolingdown: 8, maintenance: 9 };\nconst stateCode = stateCodeMap[state] ?? cache.stateCode;\n\nif (flow !== null) cache.flow = flow;\nif (power !== null) cache.power = power;\nif (ctrl !== null) cache.ctrl = ctrl;\nif (nCog !== null) cache.nCog = nCog;\nif (runtime !== null) cache.runtime = runtime;\nif (moveTimeleft !== null) cache.moveTimeleft = moveTimeleft;\nif (maintenanceTime !== null) cache.maintenanceTime = maintenanceTime;\ncache.state = state;\ncache.mode = mode;\ncache.stateCode = stateCode;\ncontext.set('metricCache', cache);\n\nreturn [\n { topic: 'actual_flow', payload: cache.flow },\n { topic: 'predicted_power', payload: cache.power },\n { topic: 'actual_ctrl', payload: cache.ctrl },\n { topic: 'nCog', payload: cache.nCog },\n { topic: 'stateCode', payload: cache.stateCode },\n { payload: 'state=' + cache.state + ', mode=' + cache.mode + ', ctrl=' + cache.ctrl.toFixed(2) + '%' },\n { payload: 'runtime=' + cache.runtime.toFixed(3) + ' h | moveTimeLeft=' + cache.moveTimeleft.toFixed(0) + ' s | maintenance=' + cache.maintenanceTime.toFixed(3) + ' h' },\n { payload: JSON.stringify(merged) }\n];",
"outputs": 8,
"noerr": 0,
"x": 1310,
"y": 420,
"wires": [
[
"rm_chart_flow"
],
[
"rm_chart_power"
],
[
"rm_chart_ctrl"
],
[
"rm_chart_ncog"
],
[
"rm_chart_statecode"
],
[
"rm_text_state"
],
[
"rm_text_timing"
],
[
"rm_text_snapshot"
]
]
},
{
"id": "rm_chart_flow",
"type": "ui-chart",
"z": "f1e8a6c8b2a4477f",
"group": "ui_group_rm_obs",
"name": "Predicted Flow",
"label": "Flow (m3/h)",
"order": 1,
"width": 12,
"height": 4,
"chartType": "line",
"category": "",
"xAxisLabel": "time",
"xAxisType": "time",
"yAxisLabel": "m3/h",
"removeOlder": "1",
"removeOlderUnit": "3600",
"removeOlderPoints": "",
"x": 1560,
"y": 340,
"wires": [],
"showLegend": true
},
{
"id": "rm_chart_power",
"type": "ui-chart",
"z": "f1e8a6c8b2a4477f",
"group": "ui_group_rm_obs",
"name": "Predicted Power",
"label": "Power (kW)",
"order": 2,
"width": 12,
"height": 4,
"chartType": "line",
"xAxisType": "time",
"yAxisLabel": "kW",
"removeOlder": "1",
"removeOlderUnit": "3600",
"x": 1560,
"y": 400,
"wires": [],
"showLegend": true
},
{
"id": "rm_chart_ctrl",
"type": "ui-chart",
"z": "f1e8a6c8b2a4477f",
"group": "ui_group_rm_obs",
"name": "Control Position",
"label": "Ctrl (%)",
"order": 3,
"width": 12,
"height": 4,
"chartType": "line",
"xAxisType": "time",
"yAxisLabel": "%",
"removeOlder": "1",
"removeOlderUnit": "3600",
"x": 1560,
"y": 460,
"wires": [],
"showLegend": true
},
{
"id": "rm_text_state",
"type": "ui-text",
"z": "f1e8a6c8b2a4477f",
"group": "ui_group_rm_obs",
"name": "State/Mode",
"label": "Current State",
"order": 6,
"width": 12,
"height": 1,
"format": "{{msg.payload}}",
"layout": "row-spread",
"x": 1560,
"y": 520,
"wires": []
},
{
"id": "rm_debug_influx",
"type": "debug",
"z": "f1e8a6c8b2a4477f",
"name": "Influx output",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"x": 1290,
"y": 560,
"wires": []
},
{
"id": "rm_debug_parent",
"type": "debug",
"z": "f1e8a6c8b2a4477f",
"name": "Parent output",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"x": 1280,
"y": 600,
"wires": []
},
{
"id": "rm_chart_ncog",
"type": "ui-chart",
"z": "f1e8a6c8b2a4477f",
"group": "ui_group_rm_obs",
"name": "NCog",
"label": "NCog (%)",
"order": 4,
"width": 12,
"height": 4,
"chartType": "line",
"xAxisType": "time",
"yAxisLabel": "%",
"removeOlder": "1",
"removeOlderUnit": "3600",
"x": 1560,
"y": 520,
"wires": [],
"showLegend": true
},
{
"id": "rm_chart_statecode",
"type": "ui-chart",
"z": "f1e8a6c8b2a4477f",
"group": "ui_group_rm_obs",
"name": "Machine State Code",
"label": "State Code (off=0 .. maint=9)",
"order": 5,
"width": 12,
"height": 4,
"chartType": "line",
"xAxisType": "time",
"yAxisLabel": "state",
"removeOlder": "1",
"removeOlderUnit": "3600",
"x": 1560,
"y": 580,
"wires": [],
"showLegend": true
},
{
"id": "rm_text_timing",
"type": "ui-text",
"z": "f1e8a6c8b2a4477f",
"group": "ui_group_rm_obs",
"name": "Timing",
"label": "Timing",
"order": 7,
"width": 12,
"height": 1,
"format": "{{msg.payload}}",
"layout": "row-spread",
"x": 1560,
"y": 700,
"wires": []
},
{
"id": "rm_text_snapshot",
"type": "ui-text",
"z": "f1e8a6c8b2a4477f",
"group": "ui_group_rm_obs",
"name": "Latest Payload",
"label": "Latest Payload",
"order": 8,
"width": 12,
"height": 1,
"format": "{{msg.payload}}",
"layout": "row-spread",
"x": 1560,
"y": 740,
"wires": []
},
{
"id": "rm_ctrl_setpoint_for_chart",
"type": "function",
"z": "f1e8a6c8b2a4477f",
"name": "ctrl setpoint series",
"func": "msg.topic = 'setpoint_ctrl';\nmsg.payload = Number(msg.payload);\nreturn Number.isFinite(msg.payload) ? msg : null;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 610,
"y": 280,
"wires": [
[
"rm_chart_ctrl"
]
]
}
]

325
examples/edge.flow.json Normal file
View File

@@ -0,0 +1,325 @@
[
{
"id": "91a88f212fb34de8",
"type": "tab",
"label": "RotatingMachine Edge Cases",
"disabled": false,
"info": "Manual edge-case driving for rotatingMachine"
},
{
"id": "ui_base_rm_edge",
"type": "ui-base",
"name": "EVOLV Demo",
"path": "/dashboard",
"includeClientData": true,
"acceptsClientConfig": [
"ui-notification",
"ui-control"
],
"showPathInSidebar": false,
"headerContent": "page",
"navigationStyle": "default",
"titleBarStyle": "default"
},
{
"id": "ui_theme_rm_edge",
"type": "ui-theme",
"name": "EVOLV Edge Theme",
"colors": {
"surface": "#ffffff",
"primary": "#0094ce",
"bgPage": "#eeeeee",
"groupBg": "#ffffff",
"groupOutline": "#cccccc"
},
"sizes": {
"density": "default",
"pagePadding": "12px",
"groupGap": "12px",
"groupBorderRadius": "4px",
"widgetGap": "12px"
}
},
{
"id": "ui_page_rm_edge",
"type": "ui-page",
"name": "RotatingMachine Edge",
"ui": "ui_base_rm_edge",
"path": "/rotating-machine-edge",
"icon": "report_problem",
"layout": "grid",
"theme": "ui_theme_rm_edge",
"breakpoints": [
{
"name": "Default",
"px": "0",
"cols": "12"
}
],
"order": 3,
"className": ""
},
{
"id": "ui_group_rm_edge_inputs",
"type": "ui-group",
"name": "Edge Input Generators",
"page": "ui_page_rm_edge",
"width": "6",
"height": "1",
"order": 1,
"showTitle": true
},
{
"id": "ui_group_rm_edge_obs",
"type": "ui-group",
"name": "Observed Responses",
"page": "ui_page_rm_edge",
"width": "6",
"height": "8",
"order": 2,
"showTitle": true
},
{
"id": "rm_node_edge",
"type": "rotatingMachine",
"z": "91a88f212fb34de8",
"name": "RM Edge",
"speed": "1",
"startup": "3",
"warmup": "3",
"shutdown": "3",
"cooldown": "3",
"movementMode": "staticspeed",
"machineCurve": "",
"uuid": "",
"supplier": "hidrostal",
"category": "machine",
"assetType": "pump-centrifugal",
"model": "hidrostal-H05K-S03R",
"unit": "m3/h",
"enableLog": false,
"logLevel": "error",
"positionVsParent": "atEquipment",
"x": 930,
"y": 300,
"wires": [
[
"rm_edge_parse",
"rm_edge_process_debug"
],
[
"rm_edge_debug_influx"
],
[
"rm_edge_debug_parent"
]
],
"hasDistance": false
},
{
"id": "rm_edge_invalid_mode_btn",
"type": "ui-button",
"z": "91a88f212fb34de8",
"group": "ui_group_rm_edge_inputs",
"name": "Invalid Mode",
"label": "Send invalid mode",
"order": 1,
"width": "6",
"height": "1",
"payload": "invalidMode",
"payloadType": "str",
"topic": "setMode",
"x": 190,
"y": 120,
"wires": [
[
"rm_node_edge"
]
]
},
{
"id": "rm_edge_neg_setpoint",
"type": "ui-number-input",
"z": "91a88f212fb34de8",
"group": "ui_group_rm_edge_inputs",
"name": "Negative Setpoint",
"label": "Negative setpoint",
"order": 2,
"width": "6",
"height": "1",
"passthru": true,
"min": -100,
"max": 0,
"step": 1,
"x": 190,
"y": 170,
"wires": [
[
"rm_edge_exec_movement"
]
]
},
{
"id": "rm_edge_exec_movement",
"type": "function",
"z": "91a88f212fb34de8",
"name": "Build execMovement",
"func": "msg.topic = 'execMovement';\nmsg.payload = {source:'GUI', action:'execMovement', setpoint:Number(msg.payload)};\nreturn msg;",
"outputs": 1,
"x": 450,
"y": 170,
"wires": [
[
"rm_node_edge"
]
]
},
{
"id": "rm_edge_bad_source_btn",
"type": "ui-button",
"z": "91a88f212fb34de8",
"group": "ui_group_rm_edge_inputs",
"name": "Bad Source",
"label": "Disallowed source action",
"order": 3,
"width": "6",
"height": "1",
"payload": "{}",
"payloadType": "str",
"x": 210,
"y": 220,
"wires": [
[
"rm_edge_bad_source_builder"
]
]
},
{
"id": "rm_edge_bad_source_builder",
"type": "function",
"z": "91a88f212fb34de8",
"name": "Build blocked execSequence",
"func": "msg.topic='execSequence';\nmsg.payload={source:'bad-source', action:'execSequence', parameter:'startup'};\nreturn msg;",
"outputs": 1,
"x": 500,
"y": 220,
"wires": [
[
"rm_node_edge"
]
]
},
{
"id": "rm_edge_bad_sim_type",
"type": "ui-button",
"z": "91a88f212fb34de8",
"group": "ui_group_rm_edge_inputs",
"name": "Unsupported Sim Type",
"label": "simulateMeasurement (bad type)",
"order": 4,
"width": "6",
"height": "1",
"payload": "{\"type\":\"unknown\",\"position\":\"downstream\",\"value\":123,\"unit\":\"mbar\"}",
"payloadType": "json",
"topic": "simulateMeasurement",
"x": 220,
"y": 270,
"wires": [
[
"rm_node_edge"
]
]
},
{
"id": "rm_edge_parse",
"type": "function",
"z": "91a88f212fb34de8",
"name": "Summarize process output",
"func": "const incoming = (msg && msg.payload && typeof msg.payload === 'object') ? msg.payload : {};\nconst merged = { ...(context.get('lastPayload') || {}), ...incoming };\ncontext.set('lastPayload', merged);\n\nconst state = merged.state == null ? 'n/a' : String(merged.state);\nconst mode = merged.mode == null ? 'n/a' : String(merged.mode);\nconst ctrl = Number(merged.ctrl);\nconst stateCodeMap = { off: 0, idle: 1, starting: 2, warmingup: 3, operational: 4, accelerating: 5, decelerating: 6, stopping: 7, coolingdown: 8, maintenance: 9 };\nconst stateCode = stateCodeMap[state] ?? -1;\n\nreturn [\n { payload: `state=${state}, mode=${mode}, ctrl=${Number.isFinite(ctrl) ? ctrl.toFixed(2) : 'n/a'}%` },\n { topic: 'stateCode', payload: stateCode }\n];",
"outputs": 2,
"x": 1230,
"y": 300,
"wires": [
[
"rm_edge_state_text"
],
[
"rm_edge_state_chart"
]
]
},
{
"id": "rm_edge_state_text",
"type": "ui-text",
"z": "91a88f212fb34de8",
"group": "ui_group_rm_edge_obs",
"name": "State Text",
"label": "Machine summary",
"order": 2,
"format": "{{msg.payload}}",
"layout": "row-spread",
"x": 1450,
"y": 300,
"wires": [],
"width": 6,
"height": 1
},
{
"id": "rm_edge_process_debug",
"type": "debug",
"z": "91a88f212fb34de8",
"name": "Process Output",
"active": true,
"tosidebar": true,
"complete": "true",
"targetType": "full",
"x": 1220,
"y": 340,
"wires": []
},
{
"id": "rm_edge_debug_influx",
"type": "debug",
"z": "91a88f212fb34de8",
"name": "Influx Output",
"active": true,
"tosidebar": true,
"complete": "true",
"targetType": "full",
"x": 1220,
"y": 380,
"wires": []
},
{
"id": "rm_edge_debug_parent",
"type": "debug",
"z": "91a88f212fb34de8",
"name": "Parent Output",
"active": true,
"tosidebar": true,
"complete": "true",
"targetType": "full",
"x": 1220,
"y": 420,
"wires": []
},
{
"id": "rm_edge_state_chart",
"type": "ui-chart",
"z": "91a88f212fb34de8",
"group": "ui_group_rm_edge_obs",
"name": "State Code",
"label": "State Code",
"order": 1,
"width": 6,
"height": 4,
"chartType": "line",
"xAxisType": "time",
"yAxisLabel": "state",
"removeOlder": "1",
"removeOlderUnit": "3600",
"x": 1230,
"y": 300,
"wires": []
}
]

View File

@@ -0,0 +1,569 @@
[
{
"id": "12f41a7b538c40db",
"type": "tab",
"label": "RotatingMachine Integration",
"disabled": false,
"info": "Manual integration-style scenario builder for rotatingMachine"
},
{
"id": "ui_base_rm_int",
"type": "ui-base",
"name": "EVOLV Demo",
"path": "/dashboard",
"includeClientData": true,
"acceptsClientConfig": [
"ui-notification",
"ui-control"
],
"showPathInSidebar": false,
"headerContent": "page",
"navigationStyle": "default",
"titleBarStyle": "default"
},
{
"id": "ui_theme_rm_int",
"type": "ui-theme",
"name": "EVOLV Integration Theme",
"colors": {
"surface": "#ffffff",
"primary": "#0094ce",
"bgPage": "#eeeeee",
"groupBg": "#ffffff",
"groupOutline": "#cccccc"
},
"sizes": {
"density": "default",
"pagePadding": "12px",
"groupGap": "12px",
"groupBorderRadius": "4px",
"widgetGap": "12px"
}
},
{
"id": "ui_page_rm_int",
"type": "ui-page",
"name": "RotatingMachine Integration",
"ui": "ui_base_rm_int",
"path": "/rotating-machine-integration",
"icon": "lan",
"layout": "grid",
"theme": "ui_theme_rm_int",
"breakpoints": [
{
"name": "Default",
"px": "0",
"cols": "12"
}
],
"order": 2,
"className": ""
},
{
"id": "ui_group_rm_int_ctrl",
"type": "ui-group",
"name": "Control Sequences",
"page": "ui_page_rm_int",
"width": "6",
"height": "1",
"order": 1,
"showTitle": true,
"className": ""
},
{
"id": "ui_group_rm_int_sim",
"type": "ui-group",
"name": "Process Simulation",
"page": "ui_page_rm_int",
"width": "6",
"height": "1",
"order": 2,
"showTitle": true,
"className": ""
},
{
"id": "ui_group_rm_int_vis",
"type": "ui-group",
"name": "Observed Behaviour",
"page": "ui_page_rm_int",
"width": "12",
"height": "20",
"order": 3,
"showTitle": true,
"className": ""
},
{
"id": "rm_node_int",
"type": "rotatingMachine",
"z": "12f41a7b538c40db",
"name": "RM Integration",
"speed": "1",
"startup": "3",
"warmup": "3",
"shutdown": "3",
"cooldown": "3",
"movementMode": "staticspeed",
"machineCurve": "",
"uuid": "",
"supplier": "hidrostal",
"category": "machine",
"assetType": "pump-centrifugal",
"model": "hidrostal-H05K-S03R",
"unit": "m3/h",
"enableLog": false,
"logLevel": "error",
"positionVsParent": "atEquipment",
"x": 1040,
"y": 360,
"wires": [
[
"rm_int_parse"
],
[
"rm_int_debug_influx"
],
[
"rm_int_debug_parent"
]
],
"hasDistance": false
},
{
"id": "rm_int_startup",
"type": "ui-button",
"z": "12f41a7b538c40db",
"group": "ui_group_rm_int_ctrl",
"name": "Startup",
"label": "Startup",
"order": 1,
"width": "3",
"height": "1",
"icon": "play_arrow",
"payload": "startup",
"payloadType": "str",
"x": 190,
"y": 120,
"wires": [
[
"rm_int_exec_seq"
]
]
},
{
"id": "rm_int_shutdown",
"type": "ui-button",
"z": "12f41a7b538c40db",
"group": "ui_group_rm_int_ctrl",
"name": "Shutdown",
"label": "Shutdown",
"order": 2,
"width": "3",
"height": "1",
"icon": "stop",
"payload": "shutdown",
"payloadType": "str",
"x": 190,
"y": 160,
"wires": [
[
"rm_int_exec_seq"
]
]
},
{
"id": "rm_int_maint",
"type": "ui-button",
"z": "12f41a7b538c40db",
"group": "ui_group_rm_int_ctrl",
"name": "Maintenance",
"label": "Enter Maintenance",
"order": 3,
"width": "6",
"height": "1",
"icon": "build",
"payload": "entermaintenance",
"payloadType": "str",
"x": 220,
"y": 200,
"wires": [
[
"rm_int_exec_seq"
]
]
},
{
"id": "rm_int_exec_seq",
"type": "function",
"z": "12f41a7b538c40db",
"name": "Build execSequence",
"func": "msg.topic = 'execSequence';\nmsg.payload = {source:'GUI', action:'execSequence', parameter: msg.payload};\nreturn msg;",
"outputs": 1,
"x": 450,
"y": 160,
"wires": [
[
"rm_node_int"
]
]
},
{
"id": "rm_int_setpoint",
"type": "ui-number-input",
"z": "12f41a7b538c40db",
"group": "ui_group_rm_int_ctrl",
"name": "Setpoint",
"label": "Setpoint %",
"order": 4,
"width": "6",
"height": "1",
"passthru": true,
"min": 0,
"max": 100,
"step": 1,
"x": 190,
"y": 260,
"wires": [
[
"rm_int_exec_movement",
"rm_int_ctrl_setpoint_for_chart"
]
]
},
{
"id": "rm_int_exec_movement",
"type": "function",
"z": "12f41a7b538c40db",
"name": "Build execMovement",
"func": "msg.topic='execMovement';\nmsg.payload={source:'GUI', action:'execMovement', setpoint:Number(msg.payload)};\nreturn msg;",
"outputs": 1,
"x": 460,
"y": 260,
"wires": [
[
"rm_node_int"
]
]
},
{
"id": "rm_int_flow_target",
"type": "ui-number-input",
"z": "12f41a7b538c40db",
"group": "ui_group_rm_int_sim",
"name": "Flow target",
"label": "Flow target (m3/h)",
"order": 1,
"width": "6",
"height": "1",
"passthru": true,
"min": 0,
"max": 1000,
"step": 5,
"x": 200,
"y": 340,
"wires": [
[
"rm_int_flow_move",
"rm_int_flow_setpoint_for_chart"
]
]
},
{
"id": "rm_int_flow_move",
"type": "function",
"z": "12f41a7b538c40db",
"name": "Build flowMovement",
"func": "msg.topic='flowMovement';\nmsg.payload={source:'GUI', action:'flowMovement', setpoint:Number(msg.payload)};\nreturn msg;",
"outputs": 1,
"x": 450,
"y": 340,
"wires": [
[
"rm_node_int"
]
]
},
{
"id": "rm_int_pressure_up",
"type": "ui-number-input",
"z": "12f41a7b538c40db",
"group": "ui_group_rm_int_sim",
"name": "Upstream Pressure",
"label": "Upstream pressure (mbar)",
"order": 2,
"width": "6",
"height": "1",
"passthru": true,
"min": 0,
"max": 3000,
"step": 10,
"x": 210,
"y": 390,
"wires": [
[
"rm_int_sim_up"
]
]
},
{
"id": "rm_int_pressure_down",
"type": "ui-number-input",
"z": "12f41a7b538c40db",
"group": "ui_group_rm_int_sim",
"name": "Downstream Pressure",
"label": "Downstream pressure (mbar)",
"order": 3,
"width": "6",
"height": "1",
"passthru": true,
"min": 0,
"max": 3000,
"step": 10,
"x": 220,
"y": 430,
"wires": [
[
"rm_int_sim_down"
]
]
},
{
"id": "rm_int_sim_up",
"type": "function",
"z": "12f41a7b538c40db",
"name": "simulate upstream pressure",
"func": "msg.topic='simulateMeasurement';\nmsg.payload={type:'pressure', position:'upstream', value:Number(msg.payload), unit:'mbar'};\nreturn msg;",
"outputs": 1,
"x": 500,
"y": 390,
"wires": [
[
"rm_node_int"
]
]
},
{
"id": "rm_int_sim_down",
"type": "function",
"z": "12f41a7b538c40db",
"name": "simulate downstream pressure",
"func": "msg.topic='simulateMeasurement';\nmsg.payload={type:'pressure', position:'downstream', value:Number(msg.payload), unit:'mbar'};\nreturn msg;",
"outputs": 1,
"x": 510,
"y": 430,
"wires": [
[
"rm_node_int"
]
]
},
{
"id": "rm_int_parse",
"type": "function",
"z": "12f41a7b538c40db",
"name": "Parse RM output",
"func": "const incoming = (msg && msg.payload && typeof msg.payload === 'object') ? msg.payload : {};\nconst lastPayload = context.get('lastPayload') || {};\nconst merged = { ...lastPayload, ...incoming };\ncontext.set('lastPayload', merged);\n\nconst cache = context.get('metricCache') || {\n flow: 0,\n power: 0,\n nCog: 0,\n ctrl: 0,\n stateCode: 0,\n state: 'idle',\n mode: 'auto',\n runtime: 0,\n moveTimeleft: 0,\n maintenanceTime: 0\n};\n\nconst pickNumber = (...keys) => {\n for (const key of keys) {\n const value = Number(merged[key]);\n if (Number.isFinite(value)) return value;\n }\n return null;\n};\n\nconst pickString = (key, fallback = null) => {\n const value = merged[key];\n if (value === undefined || value === null || value === '') return fallback;\n return String(value);\n};\n\nconst flow = pickNumber('flow.predicted.downstream.default', 'flow.predicted.downstream');\nconst power = pickNumber('power.predicted.atequipment.default', 'power.predicted.atequipment', 'power.predicted.atEquipment.default', 'power.predicted.atEquipment');\nconst nCog = pickNumber('NCogPercent', 'NCog');\nconst ctrl = pickNumber('ctrl', 'ctrl.predicted.atequipment.default', 'ctrl.predicted.atequipment', 'ctrl.predicted.atEquipment.default', 'ctrl.predicted.atEquipment');\nconst runtime = pickNumber('runtime');\nconst moveTimeleft = pickNumber('moveTimeleft');\nconst maintenanceTime = pickNumber('maintenanceTime');\nconst state = pickString('state', cache.state);\nconst mode = pickString('mode', cache.mode);\n\nconst stateCodeMap = { off: 0, idle: 1, starting: 2, warmingup: 3, operational: 4, accelerating: 5, decelerating: 6, stopping: 7, coolingdown: 8, maintenance: 9 };\nconst stateCode = stateCodeMap[state] ?? cache.stateCode;\n\nif (flow !== null) cache.flow = flow;\nif (power !== null) cache.power = power;\nif (nCog !== null) cache.nCog = nCog;\nif (ctrl !== null) cache.ctrl = ctrl;\nif (runtime !== null) cache.runtime = runtime;\nif (moveTimeleft !== null) cache.moveTimeleft = moveTimeleft;\nif (maintenanceTime !== null) cache.maintenanceTime = maintenanceTime;\ncache.state = state;\ncache.mode = mode;\ncache.stateCode = stateCode;\ncontext.set('metricCache', cache);\n\nreturn [\n { topic: 'actual_flow', payload: cache.flow },\n { topic: 'predicted_power', payload: cache.power },\n { topic: 'nCog', payload: cache.nCog },\n { topic: 'actual_ctrl', payload: cache.ctrl },\n { topic: 'stateCode', payload: cache.stateCode },\n { payload: JSON.stringify({ state: cache.state, mode: cache.mode, ctrl: cache.ctrl, runtime: cache.runtime, moveTimeleft: cache.moveTimeleft, maintenanceTime: cache.maintenanceTime }) }\n];",
"outputs": 6,
"x": 1260,
"y": 360,
"wires": [
[
"rm_int_chart_flow"
],
[
"rm_int_chart_power"
],
[
"rm_int_chart_nCog"
],
[
"rm_int_chart_ctrl"
],
[
"rm_int_chart_statecode"
],
[
"rm_int_state_text"
]
]
},
{
"id": "rm_int_chart_flow",
"type": "ui-chart",
"z": "12f41a7b538c40db",
"group": "ui_group_rm_int_vis",
"name": "Flow",
"label": "Flow (m3/h)",
"order": 1,
"width": 12,
"height": 4,
"chartType": "line",
"xAxisType": "time",
"yAxisLabel": "m3/h",
"removeOlder": "1",
"removeOlderUnit": "3600",
"x": 1510,
"y": 280,
"wires": [],
"showLegend": true
},
{
"id": "rm_int_chart_power",
"type": "ui-chart",
"z": "12f41a7b538c40db",
"group": "ui_group_rm_int_vis",
"name": "Power",
"label": "Power (kW)",
"order": 2,
"width": 12,
"height": 4,
"chartType": "line",
"xAxisType": "time",
"yAxisLabel": "kW",
"removeOlder": "1",
"removeOlderUnit": "3600",
"x": 1510,
"y": 340,
"wires": [],
"showLegend": true
},
{
"id": "rm_int_chart_nCog",
"type": "ui-chart",
"z": "12f41a7b538c40db",
"group": "ui_group_rm_int_vis",
"name": "NCog",
"label": "NCog (%)",
"order": 3,
"width": 12,
"height": 4,
"chartType": "line",
"xAxisType": "time",
"yAxisLabel": "%",
"removeOlder": "1",
"removeOlderUnit": "3600",
"x": 1510,
"y": 400,
"wires": [],
"showLegend": true
},
{
"id": "rm_int_state_text",
"type": "ui-text",
"z": "12f41a7b538c40db",
"group": "ui_group_rm_int_vis",
"name": "State",
"label": "State Snapshot",
"order": 6,
"width": 12,
"height": 1,
"format": "{{msg.payload}}",
"layout": "row-spread",
"x": 1510,
"y": 460,
"wires": []
},
{
"id": "rm_int_debug_influx",
"type": "debug",
"z": "12f41a7b538c40db",
"name": "Influx",
"active": true,
"tosidebar": true,
"complete": "true",
"targetType": "full",
"x": 1250,
"y": 520,
"wires": []
},
{
"id": "rm_int_debug_parent",
"type": "debug",
"z": "12f41a7b538c40db",
"name": "Parent",
"active": true,
"tosidebar": true,
"complete": "true",
"targetType": "full",
"x": 1240,
"y": 560,
"wires": []
},
{
"id": "rm_int_chart_ctrl",
"type": "ui-chart",
"z": "12f41a7b538c40db",
"group": "ui_group_rm_int_vis",
"name": "Ctrl",
"label": "Ctrl (%)",
"order": 4,
"width": 12,
"height": 4,
"chartType": "line",
"xAxisType": "time",
"yAxisLabel": "%",
"removeOlder": "1",
"removeOlderUnit": "3600",
"x": 1510,
"y": 460,
"wires": [],
"showLegend": true
},
{
"id": "rm_int_chart_statecode",
"type": "ui-chart",
"z": "12f41a7b538c40db",
"group": "ui_group_rm_int_vis",
"name": "State Code",
"label": "State Code (off=0 .. maint=9)",
"order": 5,
"width": 12,
"height": 4,
"chartType": "line",
"xAxisType": "time",
"yAxisLabel": "state",
"removeOlder": "1",
"removeOlderUnit": "3600",
"x": 1510,
"y": 520,
"wires": [],
"showLegend": true
},
{
"id": "rm_int_ctrl_setpoint_for_chart",
"type": "function",
"z": "12f41a7b538c40db",
"name": "ctrl setpoint series",
"func": "msg.topic = 'setpoint_ctrl';\nmsg.payload = Number(msg.payload);\nreturn Number.isFinite(msg.payload) ? msg : null;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 560,
"y": 220,
"wires": [
[
"rm_int_chart_ctrl"
]
]
},
{
"id": "rm_int_flow_setpoint_for_chart",
"type": "function",
"z": "12f41a7b538c40db",
"name": "flow setpoint series",
"func": "msg.topic = 'setpoint_flow';\nmsg.payload = Number(msg.payload);\nreturn Number.isFinite(msg.payload) ? msg : null;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 570,
"y": 330,
"wires": [
[
"rm_int_chart_flow"
]
]
}
]