before functional changes by Codex
This commit is contained in:
@@ -35,9 +35,9 @@
|
|||||||
},
|
},
|
||||||
"sizes": {
|
"sizes": {
|
||||||
"density": "default",
|
"density": "default",
|
||||||
"pagePadding": "12px",
|
"pagePadding": "14px",
|
||||||
"groupGap": "12px",
|
"groupGap": "14px",
|
||||||
"groupBorderRadius": "4px",
|
"groupBorderRadius": "6px",
|
||||||
"widgetGap": "12px"
|
"widgetGap": "12px"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -409,8 +409,8 @@
|
|||||||
"type": "function",
|
"type": "function",
|
||||||
"z": "f1e8a6c8b2a4477f",
|
"z": "f1e8a6c8b2a4477f",
|
||||||
"name": "Parse RM process output",
|
"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];",
|
"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": 8,
|
"outputs": 11,
|
||||||
"noerr": 0,
|
"noerr": 0,
|
||||||
"x": 1310,
|
"x": 1310,
|
||||||
"y": 420,
|
"y": 420,
|
||||||
@@ -430,6 +430,15 @@
|
|||||||
[
|
[
|
||||||
"rm_chart_statecode"
|
"rm_chart_statecode"
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
"rm_chart_pressure_up"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"rm_chart_pressure_down"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"rm_chart_pressure_delta"
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"rm_text_state"
|
"rm_text_state"
|
||||||
],
|
],
|
||||||
@@ -449,20 +458,57 @@
|
|||||||
"name": "Predicted Flow",
|
"name": "Predicted Flow",
|
||||||
"label": "Flow (m3/h)",
|
"label": "Flow (m3/h)",
|
||||||
"order": 1,
|
"order": 1,
|
||||||
"width": 12,
|
"width": 6,
|
||||||
"height": 4,
|
"height": 4,
|
||||||
"chartType": "line",
|
"chartType": "line",
|
||||||
"category": "",
|
"category": "topic",
|
||||||
"xAxisLabel": "time",
|
"xAxisLabel": "time",
|
||||||
"xAxisType": "time",
|
"xAxisType": "time",
|
||||||
"yAxisLabel": "m3/h",
|
"yAxisLabel": "m3/h",
|
||||||
"removeOlder": "1",
|
"removeOlder": "15",
|
||||||
"removeOlderUnit": "3600",
|
"removeOlderUnit": "60",
|
||||||
"removeOlderPoints": "",
|
"removeOlderPoints": "",
|
||||||
"x": 1560,
|
"x": 1560,
|
||||||
"y": 340,
|
"y": 340,
|
||||||
"wires": [],
|
"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",
|
"id": "rm_chart_power",
|
||||||
@@ -472,17 +518,55 @@
|
|||||||
"name": "Predicted Power",
|
"name": "Predicted Power",
|
||||||
"label": "Power (kW)",
|
"label": "Power (kW)",
|
||||||
"order": 2,
|
"order": 2,
|
||||||
"width": 12,
|
"width": 6,
|
||||||
"height": 4,
|
"height": 4,
|
||||||
"chartType": "line",
|
"chartType": "line",
|
||||||
"xAxisType": "time",
|
"xAxisType": "time",
|
||||||
"yAxisLabel": "kW",
|
"yAxisLabel": "kW",
|
||||||
"removeOlder": "1",
|
"removeOlder": "15",
|
||||||
"removeOlderUnit": "3600",
|
"removeOlderUnit": "60",
|
||||||
"x": 1560,
|
"x": 1560,
|
||||||
"y": 400,
|
"y": 400,
|
||||||
"wires": [],
|
"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",
|
"id": "rm_chart_ctrl",
|
||||||
@@ -492,17 +576,55 @@
|
|||||||
"name": "Control Position",
|
"name": "Control Position",
|
||||||
"label": "Ctrl (%)",
|
"label": "Ctrl (%)",
|
||||||
"order": 3,
|
"order": 3,
|
||||||
"width": 12,
|
"width": 6,
|
||||||
"height": 4,
|
"height": 4,
|
||||||
"chartType": "line",
|
"chartType": "line",
|
||||||
"xAxisType": "time",
|
"xAxisType": "time",
|
||||||
"yAxisLabel": "%",
|
"yAxisLabel": "%",
|
||||||
"removeOlder": "1",
|
"removeOlder": "15",
|
||||||
"removeOlderUnit": "3600",
|
"removeOlderUnit": "60",
|
||||||
"x": 1560,
|
"x": 1560,
|
||||||
"y": 460,
|
"y": 460,
|
||||||
"wires": [],
|
"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",
|
"id": "rm_text_state",
|
||||||
@@ -511,7 +633,7 @@
|
|||||||
"group": "ui_group_rm_obs",
|
"group": "ui_group_rm_obs",
|
||||||
"name": "State/Mode",
|
"name": "State/Mode",
|
||||||
"label": "Current State",
|
"label": "Current State",
|
||||||
"order": 6,
|
"order": 9,
|
||||||
"width": 12,
|
"width": 12,
|
||||||
"height": 1,
|
"height": 1,
|
||||||
"format": "{{msg.payload}}",
|
"format": "{{msg.payload}}",
|
||||||
@@ -558,17 +680,55 @@
|
|||||||
"name": "NCog",
|
"name": "NCog",
|
||||||
"label": "NCog (%)",
|
"label": "NCog (%)",
|
||||||
"order": 4,
|
"order": 4,
|
||||||
"width": 12,
|
"width": 6,
|
||||||
"height": 4,
|
"height": 4,
|
||||||
"chartType": "line",
|
"chartType": "line",
|
||||||
"xAxisType": "time",
|
"xAxisType": "time",
|
||||||
"yAxisLabel": "%",
|
"yAxisLabel": "%",
|
||||||
"removeOlder": "1",
|
"removeOlder": "15",
|
||||||
"removeOlderUnit": "3600",
|
"removeOlderUnit": "60",
|
||||||
"x": 1560,
|
"x": 1560,
|
||||||
"y": 520,
|
"y": 520,
|
||||||
"wires": [],
|
"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",
|
"id": "rm_chart_statecode",
|
||||||
@@ -578,17 +738,55 @@
|
|||||||
"name": "Machine State Code",
|
"name": "Machine State Code",
|
||||||
"label": "State Code (off=0 .. maint=9)",
|
"label": "State Code (off=0 .. maint=9)",
|
||||||
"order": 5,
|
"order": 5,
|
||||||
"width": 12,
|
"width": 6,
|
||||||
"height": 4,
|
"height": 4,
|
||||||
"chartType": "line",
|
"chartType": "line",
|
||||||
"xAxisType": "time",
|
"xAxisType": "time",
|
||||||
"yAxisLabel": "state",
|
"yAxisLabel": "state",
|
||||||
"removeOlder": "1",
|
"removeOlder": "15",
|
||||||
"removeOlderUnit": "3600",
|
"removeOlderUnit": "60",
|
||||||
"x": 1560,
|
"x": 1560,
|
||||||
"y": 580,
|
"y": 580,
|
||||||
"wires": [],
|
"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",
|
"id": "rm_text_timing",
|
||||||
@@ -597,7 +795,7 @@
|
|||||||
"group": "ui_group_rm_obs",
|
"group": "ui_group_rm_obs",
|
||||||
"name": "Timing",
|
"name": "Timing",
|
||||||
"label": "Timing",
|
"label": "Timing",
|
||||||
"order": 7,
|
"order": 10,
|
||||||
"width": 12,
|
"width": 12,
|
||||||
"height": 1,
|
"height": 1,
|
||||||
"format": "{{msg.payload}}",
|
"format": "{{msg.payload}}",
|
||||||
@@ -611,9 +809,9 @@
|
|||||||
"type": "ui-text",
|
"type": "ui-text",
|
||||||
"z": "f1e8a6c8b2a4477f",
|
"z": "f1e8a6c8b2a4477f",
|
||||||
"group": "ui_group_rm_obs",
|
"group": "ui_group_rm_obs",
|
||||||
"name": "Latest Payload",
|
"name": "Snapshot",
|
||||||
"label": "Latest Payload",
|
"label": "Snapshot",
|
||||||
"order": 8,
|
"order": 11,
|
||||||
"width": 12,
|
"width": 12,
|
||||||
"height": 1,
|
"height": 1,
|
||||||
"format": "{{msg.payload}}",
|
"format": "{{msg.payload}}",
|
||||||
@@ -640,5 +838,179 @@
|
|||||||
"rm_chart_ctrl"
|
"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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -314,6 +314,8 @@
|
|||||||
"width": 6,
|
"width": 6,
|
||||||
"height": 4,
|
"height": 4,
|
||||||
"chartType": "line",
|
"chartType": "line",
|
||||||
|
"category": "topic",
|
||||||
|
"categoryType": "msg",
|
||||||
"xAxisType": "time",
|
"xAxisType": "time",
|
||||||
"yAxisLabel": "state",
|
"yAxisLabel": "state",
|
||||||
"removeOlder": "1",
|
"removeOlder": "1",
|
||||||
|
|||||||
@@ -34,9 +34,9 @@
|
|||||||
},
|
},
|
||||||
"sizes": {
|
"sizes": {
|
||||||
"density": "default",
|
"density": "default",
|
||||||
"pagePadding": "12px",
|
"pagePadding": "14px",
|
||||||
"groupGap": "12px",
|
"groupGap": "14px",
|
||||||
"groupBorderRadius": "4px",
|
"groupBorderRadius": "6px",
|
||||||
"widgetGap": "12px"
|
"widgetGap": "12px"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
"name": "Observed Behaviour",
|
"name": "Observed Behaviour",
|
||||||
"page": "ui_page_rm_int",
|
"page": "ui_page_rm_int",
|
||||||
"width": "12",
|
"width": "12",
|
||||||
"height": "20",
|
"height": "24",
|
||||||
"order": 3,
|
"order": 3,
|
||||||
"showTitle": true,
|
"showTitle": true,
|
||||||
"className": ""
|
"className": ""
|
||||||
@@ -361,7 +361,7 @@
|
|||||||
"type": "function",
|
"type": "function",
|
||||||
"z": "12f41a7b538c40db",
|
"z": "12f41a7b538c40db",
|
||||||
"name": "Parse RM output",
|
"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,
|
"outputs": 6,
|
||||||
"x": 1260,
|
"x": 1260,
|
||||||
"y": 360,
|
"y": 360,
|
||||||
@@ -394,7 +394,7 @@
|
|||||||
"name": "Flow",
|
"name": "Flow",
|
||||||
"label": "Flow (m3/h)",
|
"label": "Flow (m3/h)",
|
||||||
"order": 1,
|
"order": 1,
|
||||||
"width": 12,
|
"width": 6,
|
||||||
"height": 4,
|
"height": 4,
|
||||||
"chartType": "line",
|
"chartType": "line",
|
||||||
"xAxisType": "time",
|
"xAxisType": "time",
|
||||||
@@ -404,7 +404,45 @@
|
|||||||
"x": 1510,
|
"x": 1510,
|
||||||
"y": 280,
|
"y": 280,
|
||||||
"wires": [],
|
"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",
|
"id": "rm_int_chart_power",
|
||||||
@@ -414,7 +452,7 @@
|
|||||||
"name": "Power",
|
"name": "Power",
|
||||||
"label": "Power (kW)",
|
"label": "Power (kW)",
|
||||||
"order": 2,
|
"order": 2,
|
||||||
"width": 12,
|
"width": 6,
|
||||||
"height": 4,
|
"height": 4,
|
||||||
"chartType": "line",
|
"chartType": "line",
|
||||||
"xAxisType": "time",
|
"xAxisType": "time",
|
||||||
@@ -424,7 +462,45 @@
|
|||||||
"x": 1510,
|
"x": 1510,
|
||||||
"y": 340,
|
"y": 340,
|
||||||
"wires": [],
|
"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",
|
"id": "rm_int_chart_nCog",
|
||||||
@@ -434,7 +510,7 @@
|
|||||||
"name": "NCog",
|
"name": "NCog",
|
||||||
"label": "NCog (%)",
|
"label": "NCog (%)",
|
||||||
"order": 3,
|
"order": 3,
|
||||||
"width": 12,
|
"width": 6,
|
||||||
"height": 4,
|
"height": 4,
|
||||||
"chartType": "line",
|
"chartType": "line",
|
||||||
"xAxisType": "time",
|
"xAxisType": "time",
|
||||||
@@ -444,7 +520,45 @@
|
|||||||
"x": 1510,
|
"x": 1510,
|
||||||
"y": 400,
|
"y": 400,
|
||||||
"wires": [],
|
"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",
|
"id": "rm_int_state_text",
|
||||||
@@ -496,7 +610,7 @@
|
|||||||
"name": "Ctrl",
|
"name": "Ctrl",
|
||||||
"label": "Ctrl (%)",
|
"label": "Ctrl (%)",
|
||||||
"order": 4,
|
"order": 4,
|
||||||
"width": 12,
|
"width": 6,
|
||||||
"height": 4,
|
"height": 4,
|
||||||
"chartType": "line",
|
"chartType": "line",
|
||||||
"xAxisType": "time",
|
"xAxisType": "time",
|
||||||
@@ -506,7 +620,45 @@
|
|||||||
"x": 1510,
|
"x": 1510,
|
||||||
"y": 460,
|
"y": 460,
|
||||||
"wires": [],
|
"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",
|
"id": "rm_int_chart_statecode",
|
||||||
@@ -516,7 +668,7 @@
|
|||||||
"name": "State Code",
|
"name": "State Code",
|
||||||
"label": "State Code (off=0 .. maint=9)",
|
"label": "State Code (off=0 .. maint=9)",
|
||||||
"order": 5,
|
"order": 5,
|
||||||
"width": 12,
|
"width": 6,
|
||||||
"height": 4,
|
"height": 4,
|
||||||
"chartType": "line",
|
"chartType": "line",
|
||||||
"xAxisType": "time",
|
"xAxisType": "time",
|
||||||
@@ -526,7 +678,45 @@
|
|||||||
"x": 1510,
|
"x": 1510,
|
||||||
"y": 520,
|
"y": 520,
|
||||||
"wires": [],
|
"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",
|
"id": "rm_int_ctrl_setpoint_for_chart",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class nodeClass {
|
|||||||
this.name = nameOfNode; // This is the name of the node, it should match the file name and the node type in Node-RED
|
this.name = nameOfNode; // This is the name of the node, it should match the file name and the node type in Node-RED
|
||||||
this.source = null; // Will hold the specific class instance
|
this.source = null; // Will hold the specific class instance
|
||||||
this.config = null; // Will hold the merged configuration
|
this.config = null; // Will hold the merged configuration
|
||||||
|
this._pressureInitWarned = false;
|
||||||
|
|
||||||
// Load default & UI config
|
// Load default & UI config
|
||||||
this._loadConfig(uiConfig,this.node);
|
this._loadConfig(uiConfig,this.node);
|
||||||
@@ -117,6 +118,22 @@ class nodeClass {
|
|||||||
try {
|
try {
|
||||||
const mode = m.currentMode;
|
const mode = m.currentMode;
|
||||||
const state = m.state.getCurrentState();
|
const state = m.state.getCurrentState();
|
||||||
|
const requiresPressurePrediction = ["operational", "warmingup", "accelerating", "decelerating"].includes(state);
|
||||||
|
const pressureStatus = typeof m.getPressureInitializationStatus === "function"
|
||||||
|
? m.getPressureInitializationStatus()
|
||||||
|
: { initialized: true };
|
||||||
|
|
||||||
|
if (requiresPressurePrediction && !pressureStatus.initialized) {
|
||||||
|
if (!this._pressureInitWarned) {
|
||||||
|
this.node.warn("Pressure input is not initialized (upstream/downstream missing). Predictions are using minimum pressure.");
|
||||||
|
this._pressureInitWarned = true;
|
||||||
|
}
|
||||||
|
return { fill: "yellow", shape: "ring", text: `${mode}: pressure not initialized` };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pressureStatus.initialized) {
|
||||||
|
this._pressureInitWarned = false;
|
||||||
|
}
|
||||||
const flow = Math.round(m.measurements.type("flow").variant("predicted").position('downstream').getCurrentValue('m3/h'));
|
const flow = Math.round(m.measurements.type("flow").variant("predicted").position('downstream').getCurrentValue('m3/h'));
|
||||||
const power = Math.round(m.measurements.type("power").variant("predicted").position('atEquipment').getCurrentValue('kW'));
|
const power = Math.round(m.measurements.type("power").variant("predicted").position('atEquipment').getCurrentValue('kW'));
|
||||||
let symbolState;
|
let symbolState;
|
||||||
@@ -234,7 +251,7 @@ class nodeClass {
|
|||||||
const influxMsg = this._output.formatMsg(raw, this.source.config, 'influxdb');
|
const influxMsg = this._output.formatMsg(raw, this.source.config, 'influxdb');
|
||||||
|
|
||||||
// Send only updated outputs on ports 0 & 1
|
// Send only updated outputs on ports 0 & 1
|
||||||
this.node.send([processMsg, influxMsg]);
|
this.node.send([processMsg, influxMsg, null]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -242,13 +259,20 @@ class nodeClass {
|
|||||||
*/
|
*/
|
||||||
_attachInputHandler() {
|
_attachInputHandler() {
|
||||||
this.node.on('input', (msg, send, done) => {
|
this.node.on('input', (msg, send, done) => {
|
||||||
/* Update to complete event based node by putting the tick function after an input event */
|
/* Update to complete event based node by putting the tick function after an input event */
|
||||||
const m = this.source;
|
const m = this.source;
|
||||||
switch(msg.topic) {
|
const nodeSend = typeof send === 'function' ? send : (outMsg) => this.node.send(outMsg);
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch(msg.topic) {
|
||||||
case 'registerChild':
|
case 'registerChild':
|
||||||
// Register this node as a child of the parent node
|
// Register this node as a child of the parent node
|
||||||
const childId = msg.payload;
|
const childId = msg.payload;
|
||||||
const childObj = this.RED.nodes.getNode(childId);
|
const childObj = this.RED.nodes.getNode(childId);
|
||||||
|
if (!childObj || !childObj.source) {
|
||||||
|
this.node.warn(`registerChild failed: child '${childId}' not found or has no source`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
m.childRegistrationUtils.registerChild(childObj.source ,msg.positionVsParent);
|
m.childRegistrationUtils.registerChild(childObj.source ,msg.positionVsParent);
|
||||||
break;
|
break;
|
||||||
case 'setMode':
|
case 'setMode':
|
||||||
@@ -292,7 +316,11 @@ class nodeClass {
|
|||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'pressure':
|
case 'pressure':
|
||||||
m.updateMeasuredPressure(value, position, context);
|
if (typeof m.updateSimulatedMeasurement === "function") {
|
||||||
|
m.updateSimulatedMeasurement(type, position, value, context);
|
||||||
|
} else {
|
||||||
|
m.updateMeasuredPressure(value, position, context);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'flow':
|
case 'flow':
|
||||||
m.updateMeasuredFlow(value, position, context);
|
m.updateMeasuredFlow(value, position, context);
|
||||||
@@ -306,14 +334,20 @@ class nodeClass {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'showWorkingCurves':
|
case 'showWorkingCurves':
|
||||||
m.showWorkingCurves();
|
nodeSend([{ ...msg, topic : "showWorkingCurves" , payload: m.showWorkingCurves() }, null, null]);
|
||||||
send({ topic : "Showing curve" , payload: m.showWorkingCurves() });
|
|
||||||
break;
|
break;
|
||||||
case 'CoG':
|
case 'CoG':
|
||||||
m.showCoG();
|
nodeSend([{ ...msg, topic : "showCoG" , payload: m.showCoG() }, null, null]);
|
||||||
send({ topic : "Showing CoG" , payload: m.showCoG() });
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (typeof done === 'function') done();
|
||||||
|
} catch (error) {
|
||||||
|
if (typeof done === 'function') {
|
||||||
|
done(error);
|
||||||
|
} else {
|
||||||
|
this.node.error(error, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,10 +87,63 @@ class Machine {
|
|||||||
|
|
||||||
this.child = {}; // object to hold child information so we know on what to subscribe
|
this.child = {}; // object to hold child information so we know on what to subscribe
|
||||||
this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility
|
this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility
|
||||||
|
this.virtualPressureChildIds = {
|
||||||
|
upstream: "dashboard-sim-upstream",
|
||||||
|
downstream: "dashboard-sim-downstream",
|
||||||
|
};
|
||||||
|
this.virtualPressureChildren = {};
|
||||||
|
this.realPressureChildIds = {
|
||||||
|
upstream: new Set(),
|
||||||
|
downstream: new Set(),
|
||||||
|
};
|
||||||
|
this._initVirtualPressureChildren();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_initVirtualPressureChildren() {
|
||||||
|
const createVirtualChild = (position) => {
|
||||||
|
const id = this.virtualPressureChildIds[position];
|
||||||
|
const name = `dashboard-sim-${position}`;
|
||||||
|
const measurements = new MeasurementContainer({
|
||||||
|
autoConvert: true,
|
||||||
|
defaultUnits: {
|
||||||
|
pressure: "mbar",
|
||||||
|
flow: this.config.general.unit,
|
||||||
|
power: "kW",
|
||||||
|
temperature: "C",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
measurements.setChildId(id);
|
||||||
|
measurements.setChildName(name);
|
||||||
|
measurements.setParentRef(this);
|
||||||
|
|
||||||
|
return {
|
||||||
|
config: {
|
||||||
|
general: { id, name },
|
||||||
|
functionality: {
|
||||||
|
softwareType: "measurement",
|
||||||
|
positionVsParent: position,
|
||||||
|
},
|
||||||
|
asset: {
|
||||||
|
type: "pressure",
|
||||||
|
unit: "mbar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
measurements,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const upstreamChild = createVirtualChild("upstream");
|
||||||
|
const downstreamChild = createVirtualChild("downstream");
|
||||||
|
this.virtualPressureChildren.upstream = upstreamChild;
|
||||||
|
this.virtualPressureChildren.downstream = downstreamChild;
|
||||||
|
|
||||||
|
this.registerChild(upstreamChild, "measurement");
|
||||||
|
this.registerChild(downstreamChild, "measurement");
|
||||||
|
}
|
||||||
|
|
||||||
_init(){
|
_init(){
|
||||||
//assume standard temperature is 20degrees
|
//assume standard temperature is 20degrees
|
||||||
this.measurements.type('temperature').variant('measured').position('atEquipment').value(15).unit('C');
|
this.measurements.type('temperature').variant('measured').position('atEquipment').value(15).unit('C');
|
||||||
@@ -118,13 +171,19 @@ class Machine {
|
|||||||
|
|
||||||
/*------------------- Register child events -------------------*/
|
/*------------------- Register child events -------------------*/
|
||||||
registerChild(child, softwareType) {
|
registerChild(child, softwareType) {
|
||||||
this.logger.debug('Setting up child event for softwaretype ' + softwareType);
|
const resolvedSoftwareType = softwareType || child?.config?.functionality?.softwareType || "measurement";
|
||||||
|
this.logger.debug('Setting up child event for softwaretype ' + resolvedSoftwareType);
|
||||||
|
|
||||||
if(softwareType === "measurement"){
|
if(resolvedSoftwareType === "measurement"){
|
||||||
const position = child.config.functionality.positionVsParent;
|
const position = String(child.config.functionality.positionVsParent || "atEquipment").toLowerCase();
|
||||||
const distance = child.config.functionality.distanceVsParent || 0;
|
|
||||||
const measurementType = child.config.asset.type;
|
const measurementType = child.config.asset.type;
|
||||||
const key = `${measurementType}_${position}`;
|
const childId = child.config?.general?.id || `${measurementType}-${position}-unknown`;
|
||||||
|
const isVirtualPressureChild = Object.values(this.virtualPressureChildIds).includes(childId);
|
||||||
|
|
||||||
|
if (measurementType === "pressure" && !isVirtualPressureChild) {
|
||||||
|
this.realPressureChildIds[position]?.add(childId);
|
||||||
|
}
|
||||||
|
|
||||||
//rebuild to measurementype.variant no position and then switch based on values not strings or names.
|
//rebuild to measurementype.variant no position and then switch based on values not strings or names.
|
||||||
const eventName = `${measurementType}.measured.${position}`;
|
const eventName = `${measurementType}.measured.${position}`;
|
||||||
|
|
||||||
@@ -140,6 +199,7 @@ class Machine {
|
|||||||
.type(measurementType)
|
.type(measurementType)
|
||||||
.variant("measured")
|
.variant("measured")
|
||||||
.position(position)
|
.position(position)
|
||||||
|
.child(childId)
|
||||||
.value(eventData.value, eventData.timestamp, eventData.unit);
|
.value(eventData.value, eventData.timestamp, eventData.unit);
|
||||||
|
|
||||||
// Call the appropriate handler
|
// Call the appropriate handler
|
||||||
@@ -439,14 +499,16 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pressureDiff = this.measurements.type('pressure').variant('measured').difference();
|
const upstreamPressure = this._getPreferredPressureValue("upstream");
|
||||||
|
const downstreamPressure = this._getPreferredPressureValue("downstream");
|
||||||
|
|
||||||
// Both upstream & downstream => differential
|
// Both upstream & downstream => differential
|
||||||
if (pressureDiff) {
|
if (upstreamPressure != null && downstreamPressure != null) {
|
||||||
this.logger.debug(`Pressure differential: ${pressureDiff.value}`);
|
const pressureDiffValue = downstreamPressure - upstreamPressure;
|
||||||
this.predictFlow.fDimension = pressureDiff.value;
|
this.logger.debug(`Pressure differential: ${pressureDiffValue}`);
|
||||||
this.predictPower.fDimension = pressureDiff.value;
|
this.predictFlow.fDimension = pressureDiffValue;
|
||||||
this.predictCtrl.fDimension = pressureDiff.value;
|
this.predictPower.fDimension = pressureDiffValue;
|
||||||
|
this.predictCtrl.fDimension = pressureDiffValue;
|
||||||
//update the cog
|
//update the cog
|
||||||
const { cog, minEfficiency } = this.calcCog();
|
const { cog, minEfficiency } = this.calcCog();
|
||||||
// calc efficiency
|
// calc efficiency
|
||||||
@@ -454,12 +516,9 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
|||||||
//update the distance from peak
|
//update the distance from peak
|
||||||
this.calcDistanceBEP(efficiency,cog,minEfficiency);
|
this.calcDistanceBEP(efficiency,cog,minEfficiency);
|
||||||
|
|
||||||
return pressureDiff.value;
|
return pressureDiffValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get downstream
|
|
||||||
const downstreamPressure = this.measurements.type('pressure').variant('measured').position('downstream').getCurrentValue();
|
|
||||||
|
|
||||||
// Only downstream => use it, warn that it's partial
|
// Only downstream => use it, warn that it's partial
|
||||||
if (downstreamPressure != null) {
|
if (downstreamPressure != null) {
|
||||||
this.logger.warn(`Using downstream pressure only for prediction: ${downstreamPressure} This is less acurate!!`);
|
this.logger.warn(`Using downstream pressure only for prediction: ${downstreamPressure} This is less acurate!!`);
|
||||||
@@ -475,6 +534,21 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
|||||||
return downstreamPressure;
|
return downstreamPressure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only upstream => use it, warn that it's partial
|
||||||
|
if (upstreamPressure != null) {
|
||||||
|
this.logger.warn(`Using upstream pressure only for prediction: ${upstreamPressure} This is less acurate!!`);
|
||||||
|
this.predictFlow.fDimension = upstreamPressure;
|
||||||
|
this.predictPower.fDimension = upstreamPressure;
|
||||||
|
this.predictCtrl.fDimension = upstreamPressure;
|
||||||
|
//update the cog
|
||||||
|
const { cog, minEfficiency } = this.calcCog();
|
||||||
|
// calc efficiency
|
||||||
|
const efficiency = this.calcEfficiency(this.predictPower.outputY, this.predictFlow.outputY, "predicted");
|
||||||
|
//update the distance from peak
|
||||||
|
this.calcDistanceBEP(efficiency,cog,minEfficiency);
|
||||||
|
return upstreamPressure;
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.error(`No valid pressure measurements available to calculate prediction using last known pressure`);
|
this.logger.error(`No valid pressure measurements available to calculate prediction using last known pressure`);
|
||||||
|
|
||||||
//set default at 0 => lowest pressure possible
|
//set default at 0 => lowest pressure possible
|
||||||
@@ -493,6 +567,80 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getPreferredPressureValue(position) {
|
||||||
|
const realIds = Array.from(this.realPressureChildIds[position] || []);
|
||||||
|
for (const childId of realIds) {
|
||||||
|
const value = this.measurements
|
||||||
|
.type("pressure")
|
||||||
|
.variant("measured")
|
||||||
|
.position(position)
|
||||||
|
.child(childId)
|
||||||
|
.getCurrentValue();
|
||||||
|
if (value != null) return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const virtualId = this.virtualPressureChildIds[position];
|
||||||
|
if (virtualId) {
|
||||||
|
const simulatedValue = this.measurements
|
||||||
|
.type("pressure")
|
||||||
|
.variant("measured")
|
||||||
|
.position(position)
|
||||||
|
.child(virtualId)
|
||||||
|
.getCurrentValue();
|
||||||
|
if (simulatedValue != null) return simulatedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.measurements
|
||||||
|
.type("pressure")
|
||||||
|
.variant("measured")
|
||||||
|
.position(position)
|
||||||
|
.getCurrentValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
getPressureInitializationStatus() {
|
||||||
|
const upstreamPressure = this._getPreferredPressureValue("upstream");
|
||||||
|
const downstreamPressure = this._getPreferredPressureValue("downstream");
|
||||||
|
|
||||||
|
const hasUpstream = upstreamPressure != null;
|
||||||
|
const hasDownstream = downstreamPressure != null;
|
||||||
|
const hasDifferential = hasUpstream && hasDownstream;
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasUpstream,
|
||||||
|
hasDownstream,
|
||||||
|
hasDifferential,
|
||||||
|
initialized: hasUpstream || hasDownstream || hasDifferential,
|
||||||
|
source: hasDifferential ? 'differential' : hasDownstream ? 'downstream' : hasUpstream ? 'upstream' : null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSimulatedMeasurement(type, position, value, context = {}) {
|
||||||
|
const normalizedType = String(type || "").toLowerCase();
|
||||||
|
const normalizedPosition = String(position || "atEquipment").toLowerCase();
|
||||||
|
|
||||||
|
if (normalizedType !== "pressure") {
|
||||||
|
this._callMeasurementHandler(normalizedType, value, normalizedPosition, context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.virtualPressureChildIds[normalizedPosition]) {
|
||||||
|
this.logger.warn(`Unsupported simulated pressure position '${normalizedPosition}'`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const child = this.virtualPressureChildren[normalizedPosition];
|
||||||
|
if (!child?.measurements) {
|
||||||
|
this.logger.error(`Virtual pressure child '${normalizedPosition}' is missing`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
child.measurements
|
||||||
|
.type("pressure")
|
||||||
|
.variant("measured")
|
||||||
|
.position(normalizedPosition)
|
||||||
|
.value(value, context.timestamp || Date.now(), context.unit || "mbar");
|
||||||
|
}
|
||||||
|
|
||||||
handleMeasuredFlow() {
|
handleMeasuredFlow() {
|
||||||
const flowDiff = this.measurements.type('flow').variant('measured').difference();
|
const flowDiff = this.measurements.type('flow').variant('measured').difference();
|
||||||
|
|
||||||
@@ -588,8 +736,9 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
|||||||
// Helper method for operational state check
|
// Helper method for operational state check
|
||||||
_isOperationalState() {
|
_isOperationalState() {
|
||||||
const state = this.state.getCurrentState();
|
const state = this.state.getCurrentState();
|
||||||
this.logger.debug(`Checking operational state ${this.state.getCurrentState()} ? ${["operational", "accelerating", "decelerating"].includes(state)}`);
|
const activeStates = ["operational", "warmingup", "accelerating", "decelerating"];
|
||||||
return ["operational", "accelerating", "decelerating"].includes(state);
|
this.logger.debug(`Checking operational state ${this.state.getCurrentState()} ? ${activeStates.includes(state)}`);
|
||||||
|
return activeStates.includes(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
//what is the internal functions that need updating when something changes that has influence on this.
|
//what is the internal functions that need updating when something changes that has influence on this.
|
||||||
@@ -822,150 +971,3 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
|||||||
|
|
||||||
module.exports = Machine;
|
module.exports = Machine;
|
||||||
|
|
||||||
/*------------------- Testing -------------------*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
curve = require('C:/Users/zn375/.node-red/public/fallbackData.json');
|
|
||||||
|
|
||||||
//import a child
|
|
||||||
const Child = require('../../measurement/src/specificClass');
|
|
||||||
|
|
||||||
console.log(`Creating child...`);
|
|
||||||
const PT1 = new Child(config={
|
|
||||||
general:{
|
|
||||||
name:"PT1",
|
|
||||||
logging:{
|
|
||||||
enabled:true,
|
|
||||||
logLevel:"debug",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
functionality:{
|
|
||||||
softwareType:"measurement",
|
|
||||||
positionVsParent:"upstream",
|
|
||||||
},
|
|
||||||
asset:{
|
|
||||||
supplier:"Vega",
|
|
||||||
category:"sensor",
|
|
||||||
type:"pressure",
|
|
||||||
model:"Vegabar 82",
|
|
||||||
unit: "mbar"
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
const PT2 = new Child(config={
|
|
||||||
general:{
|
|
||||||
name:"PT2",
|
|
||||||
logging:{
|
|
||||||
enabled:true,
|
|
||||||
logLevel:"debug",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
functionality:{
|
|
||||||
softwareType:"measurement",
|
|
||||||
positionVsParent:"upstream",
|
|
||||||
},
|
|
||||||
asset:{
|
|
||||||
supplier:"Vega",
|
|
||||||
category:"sensor",
|
|
||||||
type:"pressure",
|
|
||||||
model:"Vegabar 82",
|
|
||||||
unit: "mbar"
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
//create a machine
|
|
||||||
console.log(`Creating machine...`);
|
|
||||||
|
|
||||||
const machineConfig = {
|
|
||||||
general: {
|
|
||||||
name: "Hydrostal",
|
|
||||||
logging: {
|
|
||||||
enabled: true,
|
|
||||||
logLevel: "debug",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
asset: {
|
|
||||||
supplier: "Hydrostal",
|
|
||||||
type: "pump",
|
|
||||||
category: "centrifugal",
|
|
||||||
model: "H05K-S03R+HGM1X-X280KO", // Ensure this field is present.
|
|
||||||
machineCurve: curve["machineCurves"]["Hydrostal"]["H05K-S03R+HGM1X-X280KO"],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const stateConfig = {
|
|
||||||
general: {
|
|
||||||
logging: {
|
|
||||||
enabled: true,
|
|
||||||
logLevel: "debug",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Your custom config here (or leave empty for defaults)
|
|
||||||
movement: {
|
|
||||||
speed: 1,
|
|
||||||
},
|
|
||||||
time: {
|
|
||||||
starting: 2,
|
|
||||||
warmingup: 3,
|
|
||||||
stopping: 2,
|
|
||||||
coolingdown: 3,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const machine = new Machine(machineConfig, stateConfig);
|
|
||||||
|
|
||||||
//machine.logger.info(JSON.stringify(curve["machineCurves"]["Hydrostal"]["H05K-S03R+HGM1X-X280KO"]));
|
|
||||||
machine.logger.info(`Registering child...`);
|
|
||||||
machine.childRegistrationUtils.registerChild(PT1, "upstream");
|
|
||||||
machine.childRegistrationUtils.registerChild(PT2, "downstream");
|
|
||||||
|
|
||||||
//feed curve to the machine class
|
|
||||||
//machine.updateCurve(curve["machineCurves"]["Hydrostal"]["H05K-S03R+HGM1X-X280KO"]);
|
|
||||||
|
|
||||||
PT1.logger.info(`Enable sim...`);
|
|
||||||
PT1.toggleSimulation();
|
|
||||||
PT2.logger.info(`Enable sim...`);
|
|
||||||
PT2.toggleSimulation();
|
|
||||||
machine.getOutput();
|
|
||||||
//manual test
|
|
||||||
//machine.handleInput("parent", "execSequence", "startup");
|
|
||||||
|
|
||||||
machine.measurements.type("pressure").variant("measured").position('upstream').value(-200);
|
|
||||||
machine.measurements.type("pressure").variant("measured").position('downstream').value(1000);
|
|
||||||
|
|
||||||
testingSequences();
|
|
||||||
|
|
||||||
const tickLoop = setInterval(changeInput,1000);
|
|
||||||
|
|
||||||
function changeInput(){
|
|
||||||
PT1.logger.info(`tick...`);
|
|
||||||
PT1.tick();
|
|
||||||
PT2.tick();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function testingSequences(){
|
|
||||||
try{
|
|
||||||
console.log(` ********** Testing sequence startup... **********`);
|
|
||||||
await machine.handleInput("parent", "execSequence", "startup");
|
|
||||||
console.log(` ********** Testing movement to 15... **********`);
|
|
||||||
await machine.handleInput("parent", "execMovement", 15);
|
|
||||||
machine.getOutput();
|
|
||||||
console.log(` ********** Testing sequence shutdown... **********`);
|
|
||||||
await machine.handleInput("parent", "execSequence", "shutdown");
|
|
||||||
console.log(`********** Testing moving to setpoint 10... while in idle **********`);
|
|
||||||
await machine.handleInput("parent", "execMovement", 10);
|
|
||||||
console.log(` ********** Testing sequence emergencyStop... **********`);
|
|
||||||
await machine.handleInput("parent", "execSequence", "emergencystop");
|
|
||||||
console.log(`********** Testing sequence boot... **********`);
|
|
||||||
await machine.handleInput("parent", "execSequence", "boot");
|
|
||||||
}catch(error){
|
|
||||||
console.error(`Error: ${error}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -33,3 +33,12 @@ test('handleInput ignores disallowed source/action combination', async () => {
|
|||||||
|
|
||||||
assert.equal(before, after);
|
assert.equal(before, after);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('warmingup is treated as active for prediction updates', () => {
|
||||||
|
const machine = new Machine(
|
||||||
|
makeMachineConfig(),
|
||||||
|
makeStateConfig({ state: { current: 'warmingup' } })
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(machine._isOperationalState(), true);
|
||||||
|
});
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ test('input handler routes topics to source methods', () => {
|
|||||||
showCoG() {
|
showCoG() {
|
||||||
return { cog: 1 };
|
return { cog: 1 };
|
||||||
},
|
},
|
||||||
|
updateSimulatedMeasurement(type, position, value) {
|
||||||
|
calls.push(['updateSimulatedMeasurement', type, position, value]);
|
||||||
|
},
|
||||||
updateMeasuredPressure(value, position) {
|
updateMeasuredPressure(value, position) {
|
||||||
calls.push(['updateMeasuredPressure', value, position]);
|
calls.push(['updateMeasuredPressure', value, position]);
|
||||||
},
|
},
|
||||||
@@ -56,5 +59,83 @@ test('input handler routes topics to source methods', () => {
|
|||||||
assert.deepEqual(calls[0], ['setMode', 'auto']);
|
assert.deepEqual(calls[0], ['setMode', 'auto']);
|
||||||
assert.deepEqual(calls[1], ['handleInput', 'GUI', 'execSequence', 'startup']);
|
assert.deepEqual(calls[1], ['handleInput', 'GUI', 'execSequence', 'startup']);
|
||||||
assert.deepEqual(calls[2], ['registerChild', { id: 'child-source' }, 'downstream']);
|
assert.deepEqual(calls[2], ['registerChild', { id: 'child-source' }, 'downstream']);
|
||||||
assert.deepEqual(calls[3], ['updateMeasuredPressure', 250, 'upstream']);
|
assert.deepEqual(calls[3], ['updateSimulatedMeasurement', 'pressure', 'upstream', 250]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('status shows warning when pressure inputs are not initialized', () => {
|
||||||
|
const inst = Object.create(NodeClass.prototype);
|
||||||
|
const node = makeNodeStub();
|
||||||
|
|
||||||
|
inst.node = node;
|
||||||
|
inst.source = {
|
||||||
|
currentMode: 'virtualControl',
|
||||||
|
state: {
|
||||||
|
getCurrentState() {
|
||||||
|
return 'operational';
|
||||||
|
},
|
||||||
|
getCurrentPosition() {
|
||||||
|
return 50;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
getPressureInitializationStatus() {
|
||||||
|
return { initialized: false, hasUpstream: false, hasDownstream: false, hasDifferential: false };
|
||||||
|
},
|
||||||
|
measurements: {
|
||||||
|
type() {
|
||||||
|
return {
|
||||||
|
variant() {
|
||||||
|
return {
|
||||||
|
position() {
|
||||||
|
return { getCurrentValue() { return 0; } };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const status = inst._updateNodeStatus();
|
||||||
|
const statusAgain = inst._updateNodeStatus();
|
||||||
|
|
||||||
|
assert.equal(status.fill, 'yellow');
|
||||||
|
assert.equal(status.shape, 'ring');
|
||||||
|
assert.match(status.text, /pressure not initialized/i);
|
||||||
|
assert.equal(statusAgain.fill, 'yellow');
|
||||||
|
assert.equal(node._warns.length, 1);
|
||||||
|
assert.match(String(node._warns[0]), /Pressure input is not initialized/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('showWorkingCurves and CoG route reply messages to process output index', () => {
|
||||||
|
const inst = Object.create(NodeClass.prototype);
|
||||||
|
const node = makeNodeStub();
|
||||||
|
inst.node = node;
|
||||||
|
inst.RED = makeREDStub();
|
||||||
|
inst.source = {
|
||||||
|
childRegistrationUtils: { registerChild() {} },
|
||||||
|
setMode() {},
|
||||||
|
handleInput() {},
|
||||||
|
showWorkingCurves() {
|
||||||
|
return { curve: [1, 2, 3] };
|
||||||
|
},
|
||||||
|
showCoG() {
|
||||||
|
return { cog: 0.77 };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
inst._attachInputHandler();
|
||||||
|
const onInput = node._handlers.input;
|
||||||
|
const sent = [];
|
||||||
|
const send = (out) => sent.push(out);
|
||||||
|
|
||||||
|
onInput({ topic: 'showWorkingCurves', payload: { request: true } }, send, () => {});
|
||||||
|
onInput({ topic: 'CoG', payload: { request: true } }, send, () => {});
|
||||||
|
|
||||||
|
assert.equal(sent.length, 2);
|
||||||
|
assert.equal(Array.isArray(sent[0]), true);
|
||||||
|
assert.equal(sent[0].length, 3);
|
||||||
|
assert.equal(sent[0][0].topic, 'showWorkingCurves');
|
||||||
|
assert.equal(sent[0][1], null);
|
||||||
|
assert.equal(sent[0][2], null);
|
||||||
|
assert.equal(sent[1][0].topic, 'showCoG');
|
||||||
});
|
});
|
||||||
|
|||||||
107
test/integration/basic-flow-dashboard.integration.test.js
Normal file
107
test/integration/basic-flow-dashboard.integration.test.js
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
|
||||||
|
function loadBasicFlow() {
|
||||||
|
const flowPath = path.join(__dirname, '../../examples/basic.flow.json');
|
||||||
|
return JSON.parse(fs.readFileSync(flowPath, 'utf8'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeContextStub() {
|
||||||
|
const store = {};
|
||||||
|
return {
|
||||||
|
get(key) {
|
||||||
|
return store[key];
|
||||||
|
},
|
||||||
|
set(key, value) {
|
||||||
|
store[key] = value;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test('basic flow parser routes predicted_power to output index 2 with numeric payload', () => {
|
||||||
|
const flow = loadBasicFlow();
|
||||||
|
const parser = flow.find((n) => n.id === 'rm_parse_output');
|
||||||
|
assert.ok(parser, 'rm_parse_output node should exist');
|
||||||
|
assert.equal(parser.outputs, 11);
|
||||||
|
|
||||||
|
const func = new Function('msg', 'context', 'node', parser.func);
|
||||||
|
const context = makeContextStub();
|
||||||
|
const node = { send() {} };
|
||||||
|
|
||||||
|
const msg = {
|
||||||
|
payload: {
|
||||||
|
'flow.predicted.downstream.default': 220,
|
||||||
|
'power.predicted.atequipment.default': 50,
|
||||||
|
ctrl: 40,
|
||||||
|
NCogPercent: 72,
|
||||||
|
state: 'operational',
|
||||||
|
mode: 'virtualControl',
|
||||||
|
runtime: 10.2,
|
||||||
|
moveTimeleft: 0,
|
||||||
|
maintenanceTime: 150.5,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const out = func(msg, context, node);
|
||||||
|
assert.ok(Array.isArray(out));
|
||||||
|
assert.equal(out.length, 11);
|
||||||
|
assert.equal(out[1].topic, 'predicted_power');
|
||||||
|
assert.equal(typeof out[1].payload, 'number');
|
||||||
|
assert.ok(Number.isFinite(out[1].payload));
|
||||||
|
assert.equal(out[1].payload, 50);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('basic flow parser output index wiring matches chart nodes', () => {
|
||||||
|
const flow = loadBasicFlow();
|
||||||
|
const parser = flow.find((n) => n.id === 'rm_parse_output');
|
||||||
|
const powerChart = flow.find((n) => n.id === 'rm_chart_power');
|
||||||
|
assert.ok(parser, 'rm_parse_output node should exist');
|
||||||
|
assert.ok(powerChart, 'rm_chart_power node should exist');
|
||||||
|
|
||||||
|
assert.equal(parser.wires[1][0], 'rm_chart_power');
|
||||||
|
assert.equal(powerChart.type, 'ui-chart');
|
||||||
|
assert.equal(powerChart.chartType, 'line');
|
||||||
|
assert.equal(powerChart.xAxisType, 'time');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('basic flow parser routes pressure series to explicit pressure charts', () => {
|
||||||
|
const flow = loadBasicFlow();
|
||||||
|
const parser = flow.find((n) => n.id === 'rm_parse_output');
|
||||||
|
const upChart = flow.find((n) => n.id === 'rm_chart_pressure_up');
|
||||||
|
const downChart = flow.find((n) => n.id === 'rm_chart_pressure_down');
|
||||||
|
const deltaChart = flow.find((n) => n.id === 'rm_chart_pressure_delta');
|
||||||
|
|
||||||
|
assert.ok(parser, 'rm_parse_output node should exist');
|
||||||
|
assert.ok(upChart, 'rm_chart_pressure_up node should exist');
|
||||||
|
assert.ok(downChart, 'rm_chart_pressure_down node should exist');
|
||||||
|
assert.ok(deltaChart, 'rm_chart_pressure_delta node should exist');
|
||||||
|
|
||||||
|
assert.equal(parser.wires[5][0], 'rm_chart_pressure_up');
|
||||||
|
assert.equal(parser.wires[6][0], 'rm_chart_pressure_down');
|
||||||
|
assert.equal(parser.wires[7][0], 'rm_chart_pressure_delta');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('basic flow parser suppresses pressure chart messages when pressure inputs are incomplete', () => {
|
||||||
|
const flow = loadBasicFlow();
|
||||||
|
const parser = flow.find((n) => n.id === 'rm_parse_output');
|
||||||
|
assert.ok(parser, 'rm_parse_output node should exist');
|
||||||
|
|
||||||
|
const func = new Function('msg', 'context', 'node', parser.func);
|
||||||
|
const context = makeContextStub();
|
||||||
|
const node = { send() {} };
|
||||||
|
|
||||||
|
// Only upstream present: downstream/delta chart outputs should be null
|
||||||
|
let out = func({ payload: { 'pressure.measured.upstream.default': 950 } }, context, node);
|
||||||
|
assert.equal(out[5]?.topic, 'pressure_upstream');
|
||||||
|
assert.equal(out[6], null);
|
||||||
|
assert.equal(out[7], null);
|
||||||
|
|
||||||
|
// Once downstream arrives, delta should be emitted as finite numeric payload
|
||||||
|
out = func({ payload: { 'pressure.measured.downstream.default': 1200 } }, context, node);
|
||||||
|
assert.equal(out[6]?.topic, 'pressure_downstream');
|
||||||
|
assert.equal(out[7]?.topic, 'pressure_delta');
|
||||||
|
assert.equal(typeof out[7].payload, 'number');
|
||||||
|
assert.ok(Number.isFinite(out[7].payload));
|
||||||
|
});
|
||||||
@@ -20,3 +20,20 @@ test('calcEfficiency runs through coolprop path without mocks', () => {
|
|||||||
assert.equal(typeof eff, 'number');
|
assert.equal(typeof eff, 'number');
|
||||||
assert.ok(eff > 0);
|
assert.ok(eff > 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('predictions use initialized medium pressure and not the minimum-pressure fallback', () => {
|
||||||
|
const machine = new Machine(makeMachineConfig(), makeStateConfig({ state: { current: 'operational' } }));
|
||||||
|
|
||||||
|
const mediumUpstreamMbar = 700;
|
||||||
|
const mediumDownstreamMbar = 1100;
|
||||||
|
machine.updateMeasuredPressure(mediumUpstreamMbar, 'upstream', { timestamp: Date.now(), unit: 'mbar', childName: 'test-pt-up' });
|
||||||
|
machine.updateMeasuredPressure(mediumDownstreamMbar, 'downstream', { timestamp: Date.now(), unit: 'mbar', childName: 'test-pt-down' });
|
||||||
|
|
||||||
|
const pressureStatus = machine.getPressureInitializationStatus();
|
||||||
|
assert.equal(pressureStatus.initialized, true);
|
||||||
|
assert.equal(pressureStatus.hasDifferential, true);
|
||||||
|
|
||||||
|
const expectedDiff = mediumDownstreamMbar - mediumUpstreamMbar;
|
||||||
|
assert.equal(Math.round(machine.predictFlow.fDimension), expectedDiff);
|
||||||
|
assert.ok(machine.predictFlow.fDimension > 0);
|
||||||
|
});
|
||||||
|
|||||||
84
test/integration/pressure-initialization.integration.test.js
Normal file
84
test/integration/pressure-initialization.integration.test.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
|
||||||
|
const Machine = require('../../src/specificClass');
|
||||||
|
const { makeMachineConfig, makeStateConfig, makeChildMeasurement } = require('../helpers/factories');
|
||||||
|
|
||||||
|
test('pressure initialization combinations are handled explicitly', () => {
|
||||||
|
const createMachine = () => new Machine(makeMachineConfig(), makeStateConfig({ state: { current: 'operational' } }));
|
||||||
|
|
||||||
|
// nothing
|
||||||
|
let machine = createMachine();
|
||||||
|
let status = machine.getPressureInitializationStatus();
|
||||||
|
assert.equal(status.initialized, false);
|
||||||
|
assert.equal(status.source, null);
|
||||||
|
const noPressureValue = machine.getMeasuredPressure();
|
||||||
|
assert.equal(noPressureValue, 0);
|
||||||
|
assert.ok(machine.predictFlow.fDimension <= 1);
|
||||||
|
|
||||||
|
// upstream only
|
||||||
|
machine = createMachine();
|
||||||
|
const upstreamOnly = 850;
|
||||||
|
machine.measurements.type('pressure').variant('measured').position('upstream').value(upstreamOnly, Date.now(), 'mbar');
|
||||||
|
status = machine.getPressureInitializationStatus();
|
||||||
|
assert.equal(status.initialized, true);
|
||||||
|
assert.equal(status.hasUpstream, true);
|
||||||
|
assert.equal(status.hasDownstream, false);
|
||||||
|
assert.equal(status.hasDifferential, false);
|
||||||
|
assert.equal(status.source, 'upstream');
|
||||||
|
const upstreamValue = machine.getMeasuredPressure();
|
||||||
|
assert.equal(Math.round(upstreamValue), upstreamOnly);
|
||||||
|
assert.equal(Math.round(machine.predictFlow.fDimension), upstreamOnly);
|
||||||
|
|
||||||
|
// downstream only
|
||||||
|
machine = createMachine();
|
||||||
|
const downstreamOnly = 1150;
|
||||||
|
machine.measurements.type('pressure').variant('measured').position('downstream').value(downstreamOnly, Date.now(), 'mbar');
|
||||||
|
status = machine.getPressureInitializationStatus();
|
||||||
|
assert.equal(status.initialized, true);
|
||||||
|
assert.equal(status.hasUpstream, false);
|
||||||
|
assert.equal(status.hasDownstream, true);
|
||||||
|
assert.equal(status.hasDifferential, false);
|
||||||
|
assert.equal(status.source, 'downstream');
|
||||||
|
const downstreamValue = machine.getMeasuredPressure();
|
||||||
|
assert.equal(Math.round(downstreamValue), downstreamOnly);
|
||||||
|
assert.equal(Math.round(machine.predictFlow.fDimension), downstreamOnly);
|
||||||
|
|
||||||
|
// downstream and upstream
|
||||||
|
machine = createMachine();
|
||||||
|
const upstream = 700;
|
||||||
|
const downstream = 1100;
|
||||||
|
machine.measurements.type('pressure').variant('measured').position('upstream').value(upstream, Date.now(), 'mbar');
|
||||||
|
machine.measurements.type('pressure').variant('measured').position('downstream').value(downstream, Date.now(), 'mbar');
|
||||||
|
status = machine.getPressureInitializationStatus();
|
||||||
|
assert.equal(status.initialized, true);
|
||||||
|
assert.equal(status.hasUpstream, true);
|
||||||
|
assert.equal(status.hasDownstream, true);
|
||||||
|
assert.equal(status.hasDifferential, true);
|
||||||
|
assert.equal(status.source, 'differential');
|
||||||
|
const differentialValue = machine.getMeasuredPressure();
|
||||||
|
assert.equal(Math.round(differentialValue), downstream - upstream);
|
||||||
|
assert.equal(Math.round(machine.predictFlow.fDimension), downstream - upstream);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('real pressure child data has priority over simulated dashboard pressure', async () => {
|
||||||
|
const machine = new Machine(makeMachineConfig(), makeStateConfig({ state: { current: 'operational' } }));
|
||||||
|
|
||||||
|
machine.updateSimulatedMeasurement('pressure', 'upstream', 900, { unit: 'mbar', timestamp: Date.now() });
|
||||||
|
machine.updateSimulatedMeasurement('pressure', 'downstream', 1200, { unit: 'mbar', timestamp: Date.now() });
|
||||||
|
assert.equal(Math.round(machine.getMeasuredPressure()), 300);
|
||||||
|
|
||||||
|
const upstreamChild = makeChildMeasurement({ id: 'pt-up-real', name: 'PT Up', positionVsParent: 'upstream', type: 'pressure', unit: 'mbar' });
|
||||||
|
const downstreamChild = makeChildMeasurement({ id: 'pt-down-real', name: 'PT Down', positionVsParent: 'downstream', type: 'pressure', unit: 'mbar' });
|
||||||
|
|
||||||
|
await machine.childRegistrationUtils.registerChild(upstreamChild, 'upstream');
|
||||||
|
await machine.childRegistrationUtils.registerChild(downstreamChild, 'downstream');
|
||||||
|
|
||||||
|
upstreamChild.measurements.type('pressure').variant('measured').position('upstream').value(700, Date.now(), 'mbar');
|
||||||
|
downstreamChild.measurements.type('pressure').variant('measured').position('downstream').value(1300, Date.now(), 'mbar');
|
||||||
|
|
||||||
|
assert.equal(Math.round(machine.getMeasuredPressure()), 600);
|
||||||
|
const status = machine.getPressureInitializationStatus();
|
||||||
|
assert.equal(status.source, 'differential');
|
||||||
|
assert.equal(status.initialized, true);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user