before functional changes by Codex

This commit is contained in:
znetsixe
2026-02-19 17:36:44 +01:00
parent 405be33626
commit b5137ba9c2
10 changed files with 1118 additions and 220 deletions

View File

@@ -35,9 +35,9 @@
},
"sizes": {
"density": "default",
"pagePadding": "12px",
"groupGap": "12px",
"groupBorderRadius": "4px",
"pagePadding": "14px",
"groupGap": "14px",
"groupBorderRadius": "6px",
"widgetGap": "12px"
}
},
@@ -409,8 +409,8 @@
"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,
"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 pressureUp: null,\n pressureDown: null,\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 pickByPrefix = (...prefixes) => {\n const keys = Object.keys(merged);\n for (const prefix of prefixes) {\n const direct = Number(merged[prefix]);\n if (Number.isFinite(direct)) return direct;\n\n const dynamicKey = keys.find((k) => k === prefix || k.startsWith(prefix + '.'));\n if (!dynamicKey) continue;\n\n const value = Number(merged[dynamicKey]);\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 flowValue = pickByPrefix('flow.predicted.downstream');\nconst power = pickByPrefix('power.predicted.atequipment', 'power.predicted.atEquipment');\nconst ctrl = pickNumber('ctrl') ?? pickByPrefix('ctrl.predicted.atequipment', 'ctrl.predicted.atEquipment');\nconst nCog = pickNumber('NCogPercent', 'NCog');\nconst runtime = pickNumber('runtime');\nconst moveTimeleft = pickNumber('moveTimeleft');\nconst maintenanceTime = pickNumber('maintenanceTime');\nconst pressureDownIncoming = pickByPrefix('pressure.measured.downstream');\nconst pressureUpIncoming = pickByPrefix('pressure.measured.upstream');\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 (flowValue !== null) cache.flow = flowValue;\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;\nif (pressureUpIncoming !== null) cache.pressureUp = pressureUpIncoming;\nif (pressureDownIncoming !== null) cache.pressureDown = pressureDownIncoming;\ncache.state = state;\ncache.mode = mode;\ncache.stateCode = stateCode;\ncontext.set('metricCache', cache);\n\nconst pressureUp = Number.isFinite(cache.pressureUp) ? cache.pressureUp : null;\nconst pressureDown = Number.isFinite(cache.pressureDown) ? cache.pressureDown : null;\nconst pressureDelta = (pressureDown !== null && pressureUp !== null) ? (pressureDown - pressureUp) : null;\n\nconst now = Date.now();\nconst compactSnapshot = [\n `Q=${cache.flow.toFixed(1)} m3/h`,\n `P=${cache.power.toFixed(2)} kW`,\n `Ctrl=${cache.ctrl.toFixed(1)}%`,\n `NCog=${cache.nCog.toFixed(1)}%`,\n `Pup=${pressureUp == null ? 'n/a' : pressureUp.toFixed(0)} mbar`,\n `Pdown=${pressureDown == null ? 'n/a' : pressureDown.toFixed(0)} mbar`\n].join(' | ');\n\nreturn [\n { topic: 'actual_flow', payload: cache.flow, timestamp: now },\n { topic: 'predicted_power', payload: cache.power, timestamp: now },\n { topic: 'actual_ctrl', payload: cache.ctrl, timestamp: now },\n { topic: 'nCog', payload: cache.nCog, timestamp: now },\n { topic: 'stateCode', payload: cache.stateCode, timestamp: now },\n pressureUp === null ? null : { topic: 'pressure_upstream', payload: pressureUp, timestamp: now },\n pressureDown === null ? null : { topic: 'pressure_downstream', payload: pressureDown, timestamp: now },\n pressureDelta === null ? null : { topic: 'pressure_delta', payload: pressureDelta, timestamp: now },\n { topic: 'stateMode', payload: `state=${cache.state} | mode=${cache.mode} | ctrl=${cache.ctrl.toFixed(1)}%` },\n { topic: 'timing', payload: `runtime=${cache.runtime.toFixed(2)} h | moveLeft=${cache.moveTimeleft.toFixed(0)} s | maint=${cache.maintenanceTime.toFixed(2)} h` },\n { topic: 'snapshot', payload: compactSnapshot }\n];",
"outputs": 11,
"noerr": 0,
"x": 1310,
"y": 420,
@@ -430,6 +430,15 @@
[
"rm_chart_statecode"
],
[
"rm_chart_pressure_up"
],
[
"rm_chart_pressure_down"
],
[
"rm_chart_pressure_delta"
],
[
"rm_text_state"
],
@@ -449,20 +458,57 @@
"name": "Predicted Flow",
"label": "Flow (m3/h)",
"order": 1,
"width": 12,
"width": 6,
"height": 4,
"chartType": "line",
"category": "",
"category": "topic",
"xAxisLabel": "time",
"xAxisType": "time",
"yAxisLabel": "m3/h",
"removeOlder": "1",
"removeOlderUnit": "3600",
"removeOlder": "15",
"removeOlderUnit": "60",
"removeOlderPoints": "",
"x": 1560,
"y": 340,
"wires": [],
"showLegend": true
"showLegend": false,
"categoryType": "msg",
"xAxisProperty": "",
"xAxisPropertyType": "timestamp",
"xAxisFormat": "",
"xAxisFormatType": "auto",
"yAxisProperty": "payload",
"yAxisPropertyType": "msg",
"xmin": "",
"xmax": "",
"ymin": "",
"ymax": "",
"bins": 10,
"action": "append",
"stackSeries": false,
"pointShape": "circle",
"pointRadius": 4,
"interpolation": "linear",
"className": "",
"colors": [
"#0095FF",
"#FF0000",
"#FF7F0E",
"#2CA02C",
"#A347E1",
"#D62728",
"#FF9896",
"#9467BD",
"#C5B0D5"
],
"textColor": [
"#666666"
],
"textColorDefault": true,
"gridColor": [
"#e5e5e5"
],
"gridColorDefault": true
},
{
"id": "rm_chart_power",
@@ -472,17 +518,55 @@
"name": "Predicted Power",
"label": "Power (kW)",
"order": 2,
"width": 12,
"width": 6,
"height": 4,
"chartType": "line",
"xAxisType": "time",
"yAxisLabel": "kW",
"removeOlder": "1",
"removeOlderUnit": "3600",
"removeOlder": "15",
"removeOlderUnit": "60",
"x": 1560,
"y": 400,
"wires": [],
"showLegend": true
"showLegend": false,
"category": "topic",
"categoryType": "msg",
"xAxisProperty": "",
"xAxisPropertyType": "timestamp",
"xAxisFormat": "",
"xAxisFormatType": "auto",
"yAxisProperty": "payload",
"yAxisPropertyType": "msg",
"xmin": "",
"xmax": "",
"ymin": "",
"ymax": "",
"bins": 10,
"action": "append",
"stackSeries": false,
"pointShape": "circle",
"pointRadius": 4,
"interpolation": "linear",
"className": "",
"colors": [
"#0095FF",
"#FF0000",
"#FF7F0E",
"#2CA02C",
"#A347E1",
"#D62728",
"#FF9896",
"#9467BD",
"#C5B0D5"
],
"textColor": [
"#666666"
],
"textColorDefault": true,
"gridColor": [
"#e5e5e5"
],
"gridColorDefault": true
},
{
"id": "rm_chart_ctrl",
@@ -492,17 +576,55 @@
"name": "Control Position",
"label": "Ctrl (%)",
"order": 3,
"width": 12,
"width": 6,
"height": 4,
"chartType": "line",
"xAxisType": "time",
"yAxisLabel": "%",
"removeOlder": "1",
"removeOlderUnit": "3600",
"removeOlder": "15",
"removeOlderUnit": "60",
"x": 1560,
"y": 460,
"wires": [],
"showLegend": true
"showLegend": false,
"category": "topic",
"categoryType": "msg",
"xAxisProperty": "",
"xAxisPropertyType": "timestamp",
"xAxisFormat": "",
"xAxisFormatType": "auto",
"yAxisProperty": "payload",
"yAxisPropertyType": "msg",
"xmin": "",
"xmax": "",
"ymin": "",
"ymax": "",
"bins": 10,
"action": "append",
"stackSeries": false,
"pointShape": "circle",
"pointRadius": 4,
"interpolation": "linear",
"className": "",
"colors": [
"#0095FF",
"#FF0000",
"#FF7F0E",
"#2CA02C",
"#A347E1",
"#D62728",
"#FF9896",
"#9467BD",
"#C5B0D5"
],
"textColor": [
"#666666"
],
"textColorDefault": true,
"gridColor": [
"#e5e5e5"
],
"gridColorDefault": true
},
{
"id": "rm_text_state",
@@ -511,7 +633,7 @@
"group": "ui_group_rm_obs",
"name": "State/Mode",
"label": "Current State",
"order": 6,
"order": 9,
"width": 12,
"height": 1,
"format": "{{msg.payload}}",
@@ -558,17 +680,55 @@
"name": "NCog",
"label": "NCog (%)",
"order": 4,
"width": 12,
"width": 6,
"height": 4,
"chartType": "line",
"xAxisType": "time",
"yAxisLabel": "%",
"removeOlder": "1",
"removeOlderUnit": "3600",
"removeOlder": "15",
"removeOlderUnit": "60",
"x": 1560,
"y": 520,
"wires": [],
"showLegend": true
"showLegend": false,
"category": "topic",
"categoryType": "msg",
"xAxisProperty": "",
"xAxisPropertyType": "timestamp",
"xAxisFormat": "",
"xAxisFormatType": "auto",
"yAxisProperty": "payload",
"yAxisPropertyType": "msg",
"xmin": "",
"xmax": "",
"ymin": "",
"ymax": "",
"bins": 10,
"action": "append",
"stackSeries": false,
"pointShape": "circle",
"pointRadius": 4,
"interpolation": "linear",
"className": "",
"colors": [
"#0095FF",
"#FF0000",
"#FF7F0E",
"#2CA02C",
"#A347E1",
"#D62728",
"#FF9896",
"#9467BD",
"#C5B0D5"
],
"textColor": [
"#666666"
],
"textColorDefault": true,
"gridColor": [
"#e5e5e5"
],
"gridColorDefault": true
},
{
"id": "rm_chart_statecode",
@@ -578,17 +738,55 @@
"name": "Machine State Code",
"label": "State Code (off=0 .. maint=9)",
"order": 5,
"width": 12,
"width": 6,
"height": 4,
"chartType": "line",
"xAxisType": "time",
"yAxisLabel": "state",
"removeOlder": "1",
"removeOlderUnit": "3600",
"removeOlder": "15",
"removeOlderUnit": "60",
"x": 1560,
"y": 580,
"wires": [],
"showLegend": true
"showLegend": false,
"category": "topic",
"categoryType": "msg",
"xAxisProperty": "",
"xAxisPropertyType": "timestamp",
"xAxisFormat": "",
"xAxisFormatType": "auto",
"yAxisProperty": "payload",
"yAxisPropertyType": "msg",
"xmin": "",
"xmax": "",
"ymin": "",
"ymax": "",
"bins": 10,
"action": "append",
"stackSeries": false,
"pointShape": "circle",
"pointRadius": 4,
"interpolation": "linear",
"className": "",
"colors": [
"#0095FF",
"#FF0000",
"#FF7F0E",
"#2CA02C",
"#A347E1",
"#D62728",
"#FF9896",
"#9467BD",
"#C5B0D5"
],
"textColor": [
"#666666"
],
"textColorDefault": true,
"gridColor": [
"#e5e5e5"
],
"gridColorDefault": true
},
{
"id": "rm_text_timing",
@@ -597,7 +795,7 @@
"group": "ui_group_rm_obs",
"name": "Timing",
"label": "Timing",
"order": 7,
"order": 10,
"width": 12,
"height": 1,
"format": "{{msg.payload}}",
@@ -611,9 +809,9 @@
"type": "ui-text",
"z": "f1e8a6c8b2a4477f",
"group": "ui_group_rm_obs",
"name": "Latest Payload",
"label": "Latest Payload",
"order": 8,
"name": "Snapshot",
"label": "Snapshot",
"order": 11,
"width": 12,
"height": 1,
"format": "{{msg.payload}}",
@@ -640,5 +838,179 @@
"rm_chart_ctrl"
]
]
},
{
"id": "rm_chart_pressure_up",
"type": "ui-chart",
"z": "f1e8a6c8b2a4477f",
"group": "ui_group_rm_obs",
"name": "Pressure Upstream",
"label": "Upstream Pressure (mbar)",
"order": 6,
"width": 6,
"height": 4,
"chartType": "line",
"xAxisType": "time",
"yAxisLabel": "mbar",
"removeOlder": "15",
"removeOlderUnit": "60",
"x": 1560,
"y": 640,
"wires": [],
"showLegend": false,
"category": "topic",
"categoryType": "msg",
"xAxisProperty": "",
"xAxisPropertyType": "timestamp",
"xAxisFormat": "",
"xAxisFormatType": "auto",
"yAxisProperty": "payload",
"yAxisPropertyType": "msg",
"xmin": "",
"xmax": "",
"ymin": "",
"ymax": "",
"bins": 10,
"action": "append",
"stackSeries": false,
"pointShape": "circle",
"pointRadius": 4,
"interpolation": "linear",
"className": "",
"colors": [
"#2CA02C",
"#FF0000",
"#FF7F0E",
"#2CA02C",
"#A347E1",
"#D62728",
"#FF9896",
"#9467BD",
"#C5B0D5"
],
"textColor": [
"#666666"
],
"textColorDefault": true,
"gridColor": [
"#e5e5e5"
],
"gridColorDefault": true
},
{
"id": "rm_chart_pressure_down",
"type": "ui-chart",
"z": "f1e8a6c8b2a4477f",
"group": "ui_group_rm_obs",
"name": "Pressure Downstream",
"label": "Downstream Pressure (mbar)",
"order": 7,
"width": 6,
"height": 4,
"chartType": "line",
"xAxisType": "time",
"yAxisLabel": "mbar",
"removeOlder": "15",
"removeOlderUnit": "60",
"x": 1560,
"y": 700,
"wires": [],
"showLegend": false,
"category": "topic",
"categoryType": "msg",
"xAxisProperty": "",
"xAxisPropertyType": "timestamp",
"xAxisFormat": "",
"xAxisFormatType": "auto",
"yAxisProperty": "payload",
"yAxisPropertyType": "msg",
"xmin": "",
"xmax": "",
"ymin": "",
"ymax": "",
"bins": 10,
"action": "append",
"stackSeries": false,
"pointShape": "circle",
"pointRadius": 4,
"interpolation": "linear",
"className": "",
"colors": [
"#FF7F0E",
"#FF0000",
"#FF7F0E",
"#2CA02C",
"#A347E1",
"#D62728",
"#FF9896",
"#9467BD",
"#C5B0D5"
],
"textColor": [
"#666666"
],
"textColorDefault": true,
"gridColor": [
"#e5e5e5"
],
"gridColorDefault": true
},
{
"id": "rm_chart_pressure_delta",
"type": "ui-chart",
"z": "f1e8a6c8b2a4477f",
"group": "ui_group_rm_obs",
"name": "Pressure Differential",
"label": "Pressure Delta (mbar)",
"order": 8,
"width": 6,
"height": 4,
"chartType": "line",
"xAxisType": "time",
"yAxisLabel": "mbar",
"removeOlder": "15",
"removeOlderUnit": "60",
"x": 1560,
"y": 760,
"wires": [],
"showLegend": false,
"category": "topic",
"categoryType": "msg",
"xAxisProperty": "",
"xAxisPropertyType": "timestamp",
"xAxisFormat": "",
"xAxisFormatType": "auto",
"yAxisProperty": "payload",
"yAxisPropertyType": "msg",
"xmin": "",
"xmax": "",
"ymin": "",
"ymax": "",
"bins": 10,
"action": "append",
"stackSeries": false,
"pointShape": "circle",
"pointRadius": 4,
"interpolation": "linear",
"className": "",
"colors": [
"#0095FF",
"#FF0000",
"#FF7F0E",
"#2CA02C",
"#A347E1",
"#D62728",
"#FF9896",
"#9467BD",
"#C5B0D5"
],
"textColor": [
"#666666"
],
"textColorDefault": true,
"gridColor": [
"#e5e5e5"
],
"gridColorDefault": true
}
]

View File

@@ -314,6 +314,8 @@
"width": 6,
"height": 4,
"chartType": "line",
"category": "topic",
"categoryType": "msg",
"xAxisType": "time",
"yAxisLabel": "state",
"removeOlder": "1",

View File

@@ -34,9 +34,9 @@
},
"sizes": {
"density": "default",
"pagePadding": "12px",
"groupGap": "12px",
"groupBorderRadius": "4px",
"pagePadding": "14px",
"groupGap": "14px",
"groupBorderRadius": "6px",
"widgetGap": "12px"
}
},
@@ -87,7 +87,7 @@
"name": "Observed Behaviour",
"page": "ui_page_rm_int",
"width": "12",
"height": "20",
"height": "24",
"order": 3,
"showTitle": true,
"className": ""
@@ -361,7 +361,7 @@
"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];",
"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 pressureUp: null,\n pressureDown: null,\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 pickByPrefix = (...prefixes) => {\n const keys = Object.keys(merged);\n for (const prefix of prefixes) {\n const direct = Number(merged[prefix]);\n if (Number.isFinite(direct)) return direct;\n\n const dynamicKey = keys.find((k) => k === prefix || k.startsWith(prefix + '.'));\n if (!dynamicKey) continue;\n\n const value = Number(merged[dynamicKey]);\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 flowValue = pickByPrefix('flow.predicted.downstream');\nconst power = pickByPrefix('power.predicted.atequipment', 'power.predicted.atEquipment');\nconst ctrl = pickNumber('ctrl') ?? pickByPrefix('ctrl.predicted.atequipment', 'ctrl.predicted.atEquipment');\nconst nCog = pickNumber('NCogPercent', 'NCog');\nconst runtime = pickNumber('runtime');\nconst moveTimeleft = pickNumber('moveTimeleft');\nconst maintenanceTime = pickNumber('maintenanceTime');\nconst pressureDownIncoming = pickByPrefix('pressure.measured.downstream');\nconst pressureUpIncoming = pickByPrefix('pressure.measured.upstream');\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 (flowValue !== null) cache.flow = flowValue;\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;\nif (pressureUpIncoming !== null) cache.pressureUp = pressureUpIncoming;\nif (pressureDownIncoming !== null) cache.pressureDown = pressureDownIncoming;\ncache.state = state;\ncache.mode = mode;\ncache.stateCode = stateCode;\ncontext.set('metricCache', cache);\n\nconst pressureUp = Number.isFinite(cache.pressureUp) ? cache.pressureUp : null;\nconst pressureDown = Number.isFinite(cache.pressureDown) ? cache.pressureDown : null;\nconst pressureDelta = (pressureDown !== null && pressureUp !== null) ? (pressureDown - pressureUp) : null;\n\nconst now = Date.now();\nreturn [\n { topic: 'actual_flow', payload: cache.flow, timestamp: now },\n { topic: 'predicted_power', payload: cache.power, timestamp: now },\n { topic: 'nCog', payload: cache.nCog, timestamp: now },\n { topic: 'actual_ctrl', payload: cache.ctrl, timestamp: now },\n { topic: 'stateCode', payload: cache.stateCode, timestamp: now },\n { payload: JSON.stringify({ state: cache.state, mode: cache.mode, ctrl: cache.ctrl, runtime: cache.runtime, moveTimeleft: cache.moveTimeleft, maintenanceTime: cache.maintenanceTime, pressureUp, pressureDown, pressureDelta }) }\n];",
"outputs": 6,
"x": 1260,
"y": 360,
@@ -394,7 +394,7 @@
"name": "Flow",
"label": "Flow (m3/h)",
"order": 1,
"width": 12,
"width": 6,
"height": 4,
"chartType": "line",
"xAxisType": "time",
@@ -404,7 +404,45 @@
"x": 1510,
"y": 280,
"wires": [],
"showLegend": true
"showLegend": true,
"category": "topic",
"categoryType": "msg",
"xAxisProperty": "",
"xAxisPropertyType": "timestamp",
"xAxisFormat": "",
"xAxisFormatType": "auto",
"yAxisProperty": "payload",
"yAxisPropertyType": "msg",
"xmin": "",
"xmax": "",
"ymin": "",
"ymax": "",
"bins": 10,
"action": "append",
"stackSeries": false,
"pointShape": "circle",
"pointRadius": 4,
"interpolation": "linear",
"className": "",
"colors": [
"#0095FF",
"#FF0000",
"#FF7F0E",
"#2CA02C",
"#A347E1",
"#D62728",
"#FF9896",
"#9467BD",
"#C5B0D5"
],
"textColor": [
"#666666"
],
"textColorDefault": true,
"gridColor": [
"#e5e5e5"
],
"gridColorDefault": true
},
{
"id": "rm_int_chart_power",
@@ -414,7 +452,7 @@
"name": "Power",
"label": "Power (kW)",
"order": 2,
"width": 12,
"width": 6,
"height": 4,
"chartType": "line",
"xAxisType": "time",
@@ -424,7 +462,45 @@
"x": 1510,
"y": 340,
"wires": [],
"showLegend": true
"showLegend": true,
"category": "topic",
"categoryType": "msg",
"xAxisProperty": "",
"xAxisPropertyType": "timestamp",
"xAxisFormat": "",
"xAxisFormatType": "auto",
"yAxisProperty": "payload",
"yAxisPropertyType": "msg",
"xmin": "",
"xmax": "",
"ymin": "",
"ymax": "",
"bins": 10,
"action": "append",
"stackSeries": false,
"pointShape": "circle",
"pointRadius": 4,
"interpolation": "linear",
"className": "",
"colors": [
"#0095FF",
"#FF0000",
"#FF7F0E",
"#2CA02C",
"#A347E1",
"#D62728",
"#FF9896",
"#9467BD",
"#C5B0D5"
],
"textColor": [
"#666666"
],
"textColorDefault": true,
"gridColor": [
"#e5e5e5"
],
"gridColorDefault": true
},
{
"id": "rm_int_chart_nCog",
@@ -434,7 +510,7 @@
"name": "NCog",
"label": "NCog (%)",
"order": 3,
"width": 12,
"width": 6,
"height": 4,
"chartType": "line",
"xAxisType": "time",
@@ -444,7 +520,45 @@
"x": 1510,
"y": 400,
"wires": [],
"showLegend": true
"showLegend": true,
"category": "topic",
"categoryType": "msg",
"xAxisProperty": "",
"xAxisPropertyType": "timestamp",
"xAxisFormat": "",
"xAxisFormatType": "auto",
"yAxisProperty": "payload",
"yAxisPropertyType": "msg",
"xmin": "",
"xmax": "",
"ymin": "",
"ymax": "",
"bins": 10,
"action": "append",
"stackSeries": false,
"pointShape": "circle",
"pointRadius": 4,
"interpolation": "linear",
"className": "",
"colors": [
"#0095FF",
"#FF0000",
"#FF7F0E",
"#2CA02C",
"#A347E1",
"#D62728",
"#FF9896",
"#9467BD",
"#C5B0D5"
],
"textColor": [
"#666666"
],
"textColorDefault": true,
"gridColor": [
"#e5e5e5"
],
"gridColorDefault": true
},
{
"id": "rm_int_state_text",
@@ -496,7 +610,7 @@
"name": "Ctrl",
"label": "Ctrl (%)",
"order": 4,
"width": 12,
"width": 6,
"height": 4,
"chartType": "line",
"xAxisType": "time",
@@ -506,7 +620,45 @@
"x": 1510,
"y": 460,
"wires": [],
"showLegend": true
"showLegend": true,
"category": "topic",
"categoryType": "msg",
"xAxisProperty": "",
"xAxisPropertyType": "timestamp",
"xAxisFormat": "",
"xAxisFormatType": "auto",
"yAxisProperty": "payload",
"yAxisPropertyType": "msg",
"xmin": "",
"xmax": "",
"ymin": "",
"ymax": "",
"bins": 10,
"action": "append",
"stackSeries": false,
"pointShape": "circle",
"pointRadius": 4,
"interpolation": "linear",
"className": "",
"colors": [
"#0095FF",
"#FF0000",
"#FF7F0E",
"#2CA02C",
"#A347E1",
"#D62728",
"#FF9896",
"#9467BD",
"#C5B0D5"
],
"textColor": [
"#666666"
],
"textColorDefault": true,
"gridColor": [
"#e5e5e5"
],
"gridColorDefault": true
},
{
"id": "rm_int_chart_statecode",
@@ -516,7 +668,7 @@
"name": "State Code",
"label": "State Code (off=0 .. maint=9)",
"order": 5,
"width": 12,
"width": 6,
"height": 4,
"chartType": "line",
"xAxisType": "time",
@@ -526,7 +678,45 @@
"x": 1510,
"y": 520,
"wires": [],
"showLegend": true
"showLegend": true,
"category": "topic",
"categoryType": "msg",
"xAxisProperty": "",
"xAxisPropertyType": "timestamp",
"xAxisFormat": "",
"xAxisFormatType": "auto",
"yAxisProperty": "payload",
"yAxisPropertyType": "msg",
"xmin": "",
"xmax": "",
"ymin": "",
"ymax": "",
"bins": 10,
"action": "append",
"stackSeries": false,
"pointShape": "circle",
"pointRadius": 4,
"interpolation": "linear",
"className": "",
"colors": [
"#0095FF",
"#FF0000",
"#FF7F0E",
"#2CA02C",
"#A347E1",
"#D62728",
"#FF9896",
"#9467BD",
"#C5B0D5"
],
"textColor": [
"#666666"
],
"textColorDefault": true,
"gridColor": [
"#e5e5e5"
],
"gridColorDefault": true
},
{
"id": "rm_int_ctrl_setpoint_for_chart",