diff --git a/examples/pumpingstation-3pumps-dashboard/build_flow.py b/examples/pumpingstation-3pumps-dashboard/build_flow.py index 18ceecb..3dffced 100644 --- a/examples/pumpingstation-3pumps-dashboard/build_flow.py +++ b/examples/pumpingstation-3pumps-dashboard/build_flow.py @@ -402,8 +402,9 @@ def build_process_tab(): ], }) - # Per-pump output formatter: builds the structured event used by the - # dashboard widgets and trend feeders. + # Per-pump output formatter: builds a fat object with all fields. + # The dashboard dispatcher (on the UI tab) then splits it into + # plain-string payloads per ui-text widget. One link-out per pump. nodes.append(function_node( f"format_{pump}", TAB_PROCESS, LANE_X[4], y_section + 80, f"format {label} port 0", @@ -423,18 +424,18 @@ def build_process_tab(): " state: c.state || 'idle',\n" " mode: c.mode || 'auto',\n" " ctrl: c.ctrl != null ? Number(c.ctrl).toFixed(1) + '%' : 'n/a',\n" - " flow: flow != null ? Number(flow).toFixed(1) + ' m³/h' : 'n/a',\n" + " flow: flow != null ? Number(flow).toFixed(1) + ' m³/h' : 'n/a',\n" " power: power != null ? Number(power).toFixed(2) + ' kW' : 'n/a',\n" - " pUp: pU != null ? Number(pU).toFixed(0) : 'n/a',\n" - " pDn: pD != null ? Number(pD).toFixed(0) : 'n/a',\n" - " flowNum: flow != null ? Number(flow) : null,\n" + " pUp: pU != null ? Number(pU).toFixed(0) + ' mbar' : 'n/a',\n" + " pDn: pD != null ? Number(pD).toFixed(0) + ' mbar' : 'n/a',\n" + " flowNum: flow != null ? Number(flow) : null,\n" " powerNum: power != null ? Number(power) : null,\n" "};\n" "return msg;", outputs=1, wires=[[f"lout_evt_{pump}"]], )) - # link-out: per-pump event stream → dashboard + # link-out: one per pump → dashboard dispatcher nodes.append(link_out( f"lout_evt_{pump}", TAB_PROCESS, LANE_X[5], y_section + 80, CH_PUMP_EVT[pump], @@ -797,30 +798,56 @@ def build_ui_tab(): nodes.append(link_in( "lin_evt_mgc_dash", TAB_UI, LANE_X[0], y + 40, CH_MGC_EVT, source_out_ids=["lout_evt_mgc"], - downstream=["ui_mgc_total_flow", "ui_mgc_total_power", "ui_mgc_eff"] + downstream=["dispatch_mgc"] + )) + nodes.append(function_node( + "dispatch_mgc", TAB_UI, LANE_X[1], y + 40, + "dispatch MGC", + "const p = msg.payload || {};\n" + "return [\n" + " {payload: String(p.totalFlow || 'n/a')},\n" + " {payload: String(p.totalPower || 'n/a')},\n" + " {payload: String(p.efficiency || 'n/a')},\n" + "];", + outputs=3, + wires=[["ui_mgc_total_flow"], ["ui_mgc_total_power"], ["ui_mgc_eff"]], )) nodes.append(ui_text("ui_mgc_total_flow", TAB_UI, LANE_X[2], y + 40, g_mgc, - "MGC total flow", "Total flow", "{{msg.payload.totalFlow}}")) + "MGC total flow", "Total flow", "{{msg.payload}}")) nodes.append(ui_text("ui_mgc_total_power", TAB_UI, LANE_X[2], y + 70, g_mgc, - "MGC total power", "Total power", "{{msg.payload.totalPower}}")) + "MGC total power", "Total power", "{{msg.payload}}")) nodes.append(ui_text("ui_mgc_eff", TAB_UI, LANE_X[2], y + 100, g_mgc, - "MGC efficiency", "Group efficiency", "{{msg.payload.efficiency}}")) + "MGC efficiency", "Group efficiency", "{{msg.payload}}")) nodes.append(link_in( "lin_evt_ps_dash", TAB_UI, LANE_X[0], y + 160, CH_PS_EVT, source_out_ids=["lout_evt_ps"], - downstream=["ui_ps_state", "ui_ps_level", "ui_ps_volume", "ui_ps_qin", "ui_ps_qout"] + downstream=["dispatch_ps"] + )) + nodes.append(function_node( + "dispatch_ps", TAB_UI, LANE_X[1], y + 160, + "dispatch PS", + "const p = msg.payload || {};\n" + "return [\n" + " {payload: String(p.state || 'idle')},\n" + " {payload: String(p.level || 'n/a')},\n" + " {payload: String(p.volume || 'n/a')},\n" + " {payload: String(p.qIn || 'n/a')},\n" + " {payload: String(p.qOut || 'n/a')},\n" + "];", + outputs=5, + wires=[["ui_ps_state"], ["ui_ps_level"], ["ui_ps_volume"], ["ui_ps_qin"], ["ui_ps_qout"]], )) nodes.append(ui_text("ui_ps_state", TAB_UI, LANE_X[2], y + 160, g_ps, - "PS state", "Basin state", "{{msg.payload.state}}")) - nodes.append(ui_text("ui_ps_level", TAB_UI, LANE_X[2], y + 190, g_ps, - "PS level", "Basin level", "{{msg.payload.level}}")) - nodes.append(ui_text("ui_ps_volume", TAB_UI, LANE_X[2], y + 220, g_ps, - "PS volume","Basin volume", "{{msg.payload.volume}}")) - nodes.append(ui_text("ui_ps_qin", TAB_UI, LANE_X[2], y + 250, g_ps, - "PS Qin", "Inflow", "{{msg.payload.qIn}}")) - nodes.append(ui_text("ui_ps_qout", TAB_UI, LANE_X[2], y + 280, g_ps, - "PS Qout", "Pumped out", "{{msg.payload.qOut}}")) + "PS state", "Basin state", "{{msg.payload}}")) + nodes.append(ui_text("ui_ps_level", TAB_UI, LANE_X[2], y + 200, g_ps, + "PS level", "Basin level", "{{msg.payload}}")) + nodes.append(ui_text("ui_ps_volume", TAB_UI, LANE_X[2], y + 240, g_ps, + "PS volume","Basin volume", "{{msg.payload}}")) + nodes.append(ui_text("ui_ps_qin", TAB_UI, LANE_X[2], y + 280, g_ps, + "PS Qin", "Inflow", "{{msg.payload}}")) + nodes.append(ui_text("ui_ps_qout", TAB_UI, LANE_X[2], y + 320, g_ps, + "PS Qout", "Pumped out", "{{msg.payload}}")) # ===== SECTION: Per-pump panels ===== y_pumps_start = 1000 @@ -832,36 +859,54 @@ def build_ui_tab(): nodes.append(comment(f"c_ui_{pump}", TAB_UI, LANE_X[2], y_p, f"── {label} ──", "")) - # link-in for this pump's events + # link-in: one fat object per pump → dispatcher splits into + # plain-string payloads per ui-text widget + numeric payloads + # for trend charts. 9 outputs total. + DISPLAY_FIELDS = [ + ("State", "state"), + ("Mode", "mode"), + ("Controller %", "ctrl"), + ("Flow", "flow"), + ("Power", "power"), + ("p Upstream", "pUp"), + ("p Downstream", "pDn"), + ] nodes.append(link_in( f"lin_evt_{pump}_dash", TAB_UI, LANE_X[0], y_p + 40, CH_PUMP_EVT[pump], source_out_ids=[f"lout_evt_{pump}"], - downstream=[ - f"ui_{pump}_state", - f"ui_{pump}_mode", - f"ui_{pump}_ctrl", - f"ui_{pump}_flow", - f"ui_{pump}_power", - f"ui_{pump}_pUp", - f"ui_{pump}_pDn", - f"trend_split_{pump}", + downstream=[f"dispatch_{pump}"], + )) + # Dispatcher: takes the fat object and returns 9 outputs, each + # with a plain payload ready for a ui-text or trend chart. + nodes.append(function_node( + f"dispatch_{pump}", TAB_UI, LANE_X[1], y_p + 40, + f"dispatch {label}", + "const p = msg.payload || {};\n" + "return [\n" + " {payload: String(p.state || 'idle')},\n" + " {payload: String(p.mode || 'auto')},\n" + " {payload: String(p.ctrl || 'n/a')},\n" + " {payload: String(p.flow || 'n/a')},\n" + " {payload: String(p.power || 'n/a')},\n" + " {payload: String(p.pUp || 'n/a')},\n" + " {payload: String(p.pDn || 'n/a')},\n" + " p.flowNum != null ? {topic: '" + label + "', payload: p.flowNum} : null,\n" + " p.powerNum != null ? {topic: '" + label + "', payload: p.powerNum} : null,\n" + "];", + outputs=9, + wires=[ + [f"ui_{pump}_{f}"] for _, f in DISPLAY_FIELDS + ] + [ + ["trend_short_flow", "trend_long_flow"], # output 7: flowNum → both flow charts + ["trend_short_power", "trend_long_power"], # output 8: powerNum → both power charts ], )) - - # Status text widgets — text-only, fed by the link-in - for k, (label_txt, fmt_field) in enumerate([ - ("State", "state"), - ("Mode", "mode"), - ("Controller %", "ctrl"), - ("Flow", "flow"), - ("Power", "power"), - ("p Upstream", "pUp"), - ("p Downstream", "pDn"), - ]): + # ui-text widgets + for k, (label_txt, field) in enumerate(DISPLAY_FIELDS): nodes.append(ui_text( - f"ui_{pump}_{fmt_field}", TAB_UI, LANE_X[2], y_p + 40 + k * 30, g, + f"ui_{pump}_{field}", TAB_UI, LANE_X[2], y_p + 40 + k * 40, g, f"{label} {label_txt}", label_txt, - "{{msg.payload." + fmt_field + "}}" + "{{msg.payload}}" # plain string — FlowFuse-safe )) # Setpoint slider → wrapper → link-out → process pump (cmd:setpoint-X) @@ -914,23 +959,8 @@ def build_ui_tab(): target_in_ids=[f"lin_seq_{pump}"] )) - # Trend feeder — 2 outputs (flow / power), each wired to BOTH - # the short-term and long-term chart on their respective pages. - nodes.append(function_node( - f"trend_split_{pump}", TAB_UI, LANE_X[3], y_p + 80, - f"trend split ({label})", - "const p = msg.payload || {};\n" - "const flowMsg = p.flowNum != null ? " - "{ topic: '" + label + "', payload: Number(p.flowNum) } : null;\n" - "const powerMsg = p.powerNum != null ? " - "{ topic: '" + label + "', payload: Number(p.powerNum) } : null;\n" - "return [flowMsg, powerMsg];", - outputs=2, - wires=[ - ["trend_short_flow", "trend_long_flow"], - ["trend_short_power", "trend_long_power"], - ] - )) + # (Trend feed is handled by dispatcher outputs 7+8 above — no separate + # trend_split function needed.) # ===== Trend charts — two pages, two charts per page ===== # Short-term (10 min rolling window) and long-term (1 hour). diff --git a/examples/pumpingstation-3pumps-dashboard/flow.json b/examples/pumpingstation-3pumps-dashboard/flow.json index b3e7ade..55a701c 100644 --- a/examples/pumpingstation-3pumps-dashboard/flow.json +++ b/examples/pumpingstation-3pumps-dashboard/flow.json @@ -210,7 +210,7 @@ "type": "function", "z": "tab_process", "name": "format Pump A port 0", - "func": "const p = msg.payload || {};\nconst c = context.get('c') || {};\nObject.assign(c, p);\ncontext.set('c', c);\nfunction find(prefix) {\n for (const k in c) { if (k.indexOf(prefix) === 0) return c[k]; }\n return null;\n}\nconst flow = find('flow.predicted.downstream.');\nconst power = find('power.predicted.atequipment.');\nconst pU = find('pressure.measured.upstream.');\nconst pD = find('pressure.measured.downstream.');\nmsg.payload = {\n state: c.state || 'idle',\n mode: c.mode || 'auto',\n ctrl: c.ctrl != null ? Number(c.ctrl).toFixed(1) + '%' : 'n/a',\n flow: flow != null ? Number(flow).toFixed(1) + ' m\u00b3/h' : 'n/a',\n power: power != null ? Number(power).toFixed(2) + ' kW' : 'n/a',\n pUp: pU != null ? Number(pU).toFixed(0) : 'n/a',\n pDn: pD != null ? Number(pD).toFixed(0) : 'n/a',\n flowNum: flow != null ? Number(flow) : null,\n powerNum: power != null ? Number(power) : null,\n};\nreturn msg;", + "func": "const p = msg.payload || {};\nconst c = context.get('c') || {};\nObject.assign(c, p);\ncontext.set('c', c);\nfunction find(prefix) {\n for (const k in c) { if (k.indexOf(prefix) === 0) return c[k]; }\n return null;\n}\nconst flow = find('flow.predicted.downstream.');\nconst power = find('power.predicted.atequipment.');\nconst pU = find('pressure.measured.upstream.');\nconst pD = find('pressure.measured.downstream.');\nmsg.payload = {\n state: c.state || 'idle',\n mode: c.mode || 'auto',\n ctrl: c.ctrl != null ? Number(c.ctrl).toFixed(1) + '%' : 'n/a',\n flow: flow != null ? Number(flow).toFixed(1) + ' m\u00b3/h' : 'n/a',\n power: power != null ? Number(power).toFixed(2) + ' kW' : 'n/a',\n pUp: pU != null ? Number(pU).toFixed(0) + ' mbar' : 'n/a',\n pDn: pD != null ? Number(pD).toFixed(0) + ' mbar' : 'n/a',\n flowNum: flow != null ? Number(flow) : null,\n powerNum: power != null ? Number(power) : null,\n};\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", @@ -431,7 +431,7 @@ "type": "function", "z": "tab_process", "name": "format Pump B port 0", - "func": "const p = msg.payload || {};\nconst c = context.get('c') || {};\nObject.assign(c, p);\ncontext.set('c', c);\nfunction find(prefix) {\n for (const k in c) { if (k.indexOf(prefix) === 0) return c[k]; }\n return null;\n}\nconst flow = find('flow.predicted.downstream.');\nconst power = find('power.predicted.atequipment.');\nconst pU = find('pressure.measured.upstream.');\nconst pD = find('pressure.measured.downstream.');\nmsg.payload = {\n state: c.state || 'idle',\n mode: c.mode || 'auto',\n ctrl: c.ctrl != null ? Number(c.ctrl).toFixed(1) + '%' : 'n/a',\n flow: flow != null ? Number(flow).toFixed(1) + ' m\u00b3/h' : 'n/a',\n power: power != null ? Number(power).toFixed(2) + ' kW' : 'n/a',\n pUp: pU != null ? Number(pU).toFixed(0) : 'n/a',\n pDn: pD != null ? Number(pD).toFixed(0) : 'n/a',\n flowNum: flow != null ? Number(flow) : null,\n powerNum: power != null ? Number(power) : null,\n};\nreturn msg;", + "func": "const p = msg.payload || {};\nconst c = context.get('c') || {};\nObject.assign(c, p);\ncontext.set('c', c);\nfunction find(prefix) {\n for (const k in c) { if (k.indexOf(prefix) === 0) return c[k]; }\n return null;\n}\nconst flow = find('flow.predicted.downstream.');\nconst power = find('power.predicted.atequipment.');\nconst pU = find('pressure.measured.upstream.');\nconst pD = find('pressure.measured.downstream.');\nmsg.payload = {\n state: c.state || 'idle',\n mode: c.mode || 'auto',\n ctrl: c.ctrl != null ? Number(c.ctrl).toFixed(1) + '%' : 'n/a',\n flow: flow != null ? Number(flow).toFixed(1) + ' m\u00b3/h' : 'n/a',\n power: power != null ? Number(power).toFixed(2) + ' kW' : 'n/a',\n pUp: pU != null ? Number(pU).toFixed(0) + ' mbar' : 'n/a',\n pDn: pD != null ? Number(pD).toFixed(0) + ' mbar' : 'n/a',\n flowNum: flow != null ? Number(flow) : null,\n powerNum: power != null ? Number(power) : null,\n};\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", @@ -652,7 +652,7 @@ "type": "function", "z": "tab_process", "name": "format Pump C port 0", - "func": "const p = msg.payload || {};\nconst c = context.get('c') || {};\nObject.assign(c, p);\ncontext.set('c', c);\nfunction find(prefix) {\n for (const k in c) { if (k.indexOf(prefix) === 0) return c[k]; }\n return null;\n}\nconst flow = find('flow.predicted.downstream.');\nconst power = find('power.predicted.atequipment.');\nconst pU = find('pressure.measured.upstream.');\nconst pD = find('pressure.measured.downstream.');\nmsg.payload = {\n state: c.state || 'idle',\n mode: c.mode || 'auto',\n ctrl: c.ctrl != null ? Number(c.ctrl).toFixed(1) + '%' : 'n/a',\n flow: flow != null ? Number(flow).toFixed(1) + ' m\u00b3/h' : 'n/a',\n power: power != null ? Number(power).toFixed(2) + ' kW' : 'n/a',\n pUp: pU != null ? Number(pU).toFixed(0) : 'n/a',\n pDn: pD != null ? Number(pD).toFixed(0) : 'n/a',\n flowNum: flow != null ? Number(flow) : null,\n powerNum: power != null ? Number(power) : null,\n};\nreturn msg;", + "func": "const p = msg.payload || {};\nconst c = context.get('c') || {};\nObject.assign(c, p);\ncontext.set('c', c);\nfunction find(prefix) {\n for (const k in c) { if (k.indexOf(prefix) === 0) return c[k]; }\n return null;\n}\nconst flow = find('flow.predicted.downstream.');\nconst power = find('power.predicted.atequipment.');\nconst pU = find('pressure.measured.upstream.');\nconst pD = find('pressure.measured.downstream.');\nmsg.payload = {\n state: c.state || 'idle',\n mode: c.mode || 'auto',\n ctrl: c.ctrl != null ? Number(c.ctrl).toFixed(1) + '%' : 'n/a',\n flow: flow != null ? Number(flow).toFixed(1) + ' m\u00b3/h' : 'n/a',\n power: power != null ? Number(power).toFixed(2) + ' kW' : 'n/a',\n pUp: pU != null ? Number(pU).toFixed(0) + ' mbar' : 'n/a',\n pDn: pD != null ? Number(pD).toFixed(0) + ' mbar' : 'n/a',\n flowNum: flow != null ? Number(flow) : null,\n powerNum: power != null ? Number(power) : null,\n};\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", @@ -1749,8 +1749,31 @@ "y": 640, "wires": [ [ - "ui_mgc_total_flow", - "ui_mgc_total_power", + "dispatch_mgc" + ] + ] + }, + { + "id": "dispatch_mgc", + "type": "function", + "z": "tab_ui", + "name": "dispatch MGC", + "func": "const p = msg.payload || {};\nreturn [\n {payload: String(p.totalFlow || 'n/a')},\n {payload: String(p.totalPower || 'n/a')},\n {payload: String(p.efficiency || 'n/a')},\n];", + "outputs": 3, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 380, + "y": 640, + "wires": [ + [ + "ui_mgc_total_flow" + ], + [ + "ui_mgc_total_power" + ], + [ "ui_mgc_eff" ] ] @@ -1765,7 +1788,7 @@ "height": "0", "name": "MGC total flow", "label": "Total flow", - "format": "{{msg.payload.totalFlow}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", @@ -1785,7 +1808,7 @@ "height": "0", "name": "MGC total power", "label": "Total power", - "format": "{{msg.payload.totalPower}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", @@ -1805,7 +1828,7 @@ "height": "0", "name": "MGC efficiency", "label": "Group efficiency", - "format": "{{msg.payload.efficiency}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", @@ -1827,10 +1850,37 @@ "y": 760, "wires": [ [ - "ui_ps_state", - "ui_ps_level", - "ui_ps_volume", - "ui_ps_qin", + "dispatch_ps" + ] + ] + }, + { + "id": "dispatch_ps", + "type": "function", + "z": "tab_ui", + "name": "dispatch PS", + "func": "const p = msg.payload || {};\nreturn [\n {payload: String(p.state || 'idle')},\n {payload: String(p.level || 'n/a')},\n {payload: String(p.volume || 'n/a')},\n {payload: String(p.qIn || 'n/a')},\n {payload: String(p.qOut || 'n/a')},\n];", + "outputs": 5, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 380, + "y": 760, + "wires": [ + [ + "ui_ps_state" + ], + [ + "ui_ps_level" + ], + [ + "ui_ps_volume" + ], + [ + "ui_ps_qin" + ], + [ "ui_ps_qout" ] ] @@ -1845,7 +1895,7 @@ "height": "0", "name": "PS state", "label": "Basin state", - "format": "{{msg.payload.state}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", @@ -1865,14 +1915,14 @@ "height": "0", "name": "PS level", "label": "Basin level", - "format": "{{msg.payload.level}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 14, "color": "#000000", "x": 640, - "y": 790, + "y": 800, "wires": [] }, { @@ -1885,14 +1935,14 @@ "height": "0", "name": "PS volume", "label": "Basin volume", - "format": "{{msg.payload.volume}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 14, "color": "#000000", "x": 640, - "y": 820, + "y": 840, "wires": [] }, { @@ -1905,14 +1955,14 @@ "height": "0", "name": "PS Qin", "label": "Inflow", - "format": "{{msg.payload.qIn}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 14, "color": "#000000", "x": 640, - "y": 850, + "y": 880, "wires": [] }, { @@ -1925,14 +1975,14 @@ "height": "0", "name": "PS Qout", "label": "Pumped out", - "format": "{{msg.payload.qOut}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 14, "color": "#000000", "x": 640, - "y": 880, + "y": 920, "wires": [] }, { @@ -1957,14 +2007,52 @@ "y": 1040, "wires": [ [ - "ui_pump_a_state", - "ui_pump_a_mode", - "ui_pump_a_ctrl", - "ui_pump_a_flow", - "ui_pump_a_power", - "ui_pump_a_pUp", - "ui_pump_a_pDn", - "trend_split_pump_a" + "dispatch_pump_a" + ] + ] + }, + { + "id": "dispatch_pump_a", + "type": "function", + "z": "tab_ui", + "name": "dispatch Pump A", + "func": "const p = msg.payload || {};\nreturn [\n {payload: String(p.state || 'idle')},\n {payload: String(p.mode || 'auto')},\n {payload: String(p.ctrl || 'n/a')},\n {payload: String(p.flow || 'n/a')},\n {payload: String(p.power || 'n/a')},\n {payload: String(p.pUp || 'n/a')},\n {payload: String(p.pDn || 'n/a')},\n p.flowNum != null ? {topic: 'Pump A', payload: p.flowNum} : null,\n p.powerNum != null ? {topic: 'Pump A', payload: p.powerNum} : null,\n];", + "outputs": 9, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 380, + "y": 1040, + "wires": [ + [ + "ui_pump_a_state" + ], + [ + "ui_pump_a_mode" + ], + [ + "ui_pump_a_ctrl" + ], + [ + "ui_pump_a_flow" + ], + [ + "ui_pump_a_power" + ], + [ + "ui_pump_a_pUp" + ], + [ + "ui_pump_a_pDn" + ], + [ + "trend_short_flow", + "trend_long_flow" + ], + [ + "trend_short_power", + "trend_long_power" ] ] }, @@ -1978,7 +2066,7 @@ "height": "0", "name": "Pump A State", "label": "State", - "format": "{{msg.payload.state}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", @@ -1998,14 +2086,14 @@ "height": "0", "name": "Pump A Mode", "label": "Mode", - "format": "{{msg.payload.mode}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 14, "color": "#000000", "x": 640, - "y": 1070, + "y": 1080, "wires": [] }, { @@ -2018,14 +2106,14 @@ "height": "0", "name": "Pump A Controller %", "label": "Controller %", - "format": "{{msg.payload.ctrl}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 14, "color": "#000000", "x": 640, - "y": 1100, + "y": 1120, "wires": [] }, { @@ -2038,14 +2126,14 @@ "height": "0", "name": "Pump A Flow", "label": "Flow", - "format": "{{msg.payload.flow}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 14, "color": "#000000", "x": 640, - "y": 1130, + "y": 1160, "wires": [] }, { @@ -2058,14 +2146,14 @@ "height": "0", "name": "Pump A Power", "label": "Power", - "format": "{{msg.payload.power}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 14, "color": "#000000", "x": 640, - "y": 1160, + "y": 1200, "wires": [] }, { @@ -2078,14 +2166,14 @@ "height": "0", "name": "Pump A p Upstream", "label": "p Upstream", - "format": "{{msg.payload.pUp}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 14, "color": "#000000", "x": 640, - "y": 1190, + "y": 1240, "wires": [] }, { @@ -2098,14 +2186,14 @@ "height": "0", "name": "Pump A p Downstream", "label": "p Downstream", - "format": "{{msg.payload.pDn}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 14, "color": "#000000", "x": 640, - "y": 1220, + "y": 1280, "wires": [] }, { @@ -2263,30 +2351,6 @@ "y": 1355, "wires": [] }, - { - "id": "trend_split_pump_a", - "type": "function", - "z": "tab_ui", - "name": "trend split (Pump A)", - "func": "const p = msg.payload || {};\nconst flowMsg = p.flowNum != null ? { topic: 'Pump A', payload: Number(p.flowNum) } : null;\nconst powerMsg = p.powerNum != null ? { topic: 'Pump A', payload: Number(p.powerNum) } : null;\nreturn [flowMsg, powerMsg];", - "outputs": 2, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 900, - "y": 1080, - "wires": [ - [ - "trend_short_flow", - "trend_long_flow" - ], - [ - "trend_short_power", - "trend_long_power" - ] - ] - }, { "id": "c_ui_pump_b", "type": "comment", @@ -2309,14 +2373,52 @@ "y": 1440, "wires": [ [ - "ui_pump_b_state", - "ui_pump_b_mode", - "ui_pump_b_ctrl", - "ui_pump_b_flow", - "ui_pump_b_power", - "ui_pump_b_pUp", - "ui_pump_b_pDn", - "trend_split_pump_b" + "dispatch_pump_b" + ] + ] + }, + { + "id": "dispatch_pump_b", + "type": "function", + "z": "tab_ui", + "name": "dispatch Pump B", + "func": "const p = msg.payload || {};\nreturn [\n {payload: String(p.state || 'idle')},\n {payload: String(p.mode || 'auto')},\n {payload: String(p.ctrl || 'n/a')},\n {payload: String(p.flow || 'n/a')},\n {payload: String(p.power || 'n/a')},\n {payload: String(p.pUp || 'n/a')},\n {payload: String(p.pDn || 'n/a')},\n p.flowNum != null ? {topic: 'Pump B', payload: p.flowNum} : null,\n p.powerNum != null ? {topic: 'Pump B', payload: p.powerNum} : null,\n];", + "outputs": 9, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 380, + "y": 1440, + "wires": [ + [ + "ui_pump_b_state" + ], + [ + "ui_pump_b_mode" + ], + [ + "ui_pump_b_ctrl" + ], + [ + "ui_pump_b_flow" + ], + [ + "ui_pump_b_power" + ], + [ + "ui_pump_b_pUp" + ], + [ + "ui_pump_b_pDn" + ], + [ + "trend_short_flow", + "trend_long_flow" + ], + [ + "trend_short_power", + "trend_long_power" ] ] }, @@ -2330,7 +2432,7 @@ "height": "0", "name": "Pump B State", "label": "State", - "format": "{{msg.payload.state}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", @@ -2350,14 +2452,14 @@ "height": "0", "name": "Pump B Mode", "label": "Mode", - "format": "{{msg.payload.mode}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 14, "color": "#000000", "x": 640, - "y": 1470, + "y": 1480, "wires": [] }, { @@ -2370,14 +2472,14 @@ "height": "0", "name": "Pump B Controller %", "label": "Controller %", - "format": "{{msg.payload.ctrl}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 14, "color": "#000000", "x": 640, - "y": 1500, + "y": 1520, "wires": [] }, { @@ -2390,14 +2492,14 @@ "height": "0", "name": "Pump B Flow", "label": "Flow", - "format": "{{msg.payload.flow}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 14, "color": "#000000", "x": 640, - "y": 1530, + "y": 1560, "wires": [] }, { @@ -2410,14 +2512,14 @@ "height": "0", "name": "Pump B Power", "label": "Power", - "format": "{{msg.payload.power}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 14, "color": "#000000", "x": 640, - "y": 1560, + "y": 1600, "wires": [] }, { @@ -2430,14 +2532,14 @@ "height": "0", "name": "Pump B p Upstream", "label": "p Upstream", - "format": "{{msg.payload.pUp}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 14, "color": "#000000", "x": 640, - "y": 1590, + "y": 1640, "wires": [] }, { @@ -2450,14 +2552,14 @@ "height": "0", "name": "Pump B p Downstream", "label": "p Downstream", - "format": "{{msg.payload.pDn}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 14, "color": "#000000", "x": 640, - "y": 1620, + "y": 1680, "wires": [] }, { @@ -2615,30 +2717,6 @@ "y": 1755, "wires": [] }, - { - "id": "trend_split_pump_b", - "type": "function", - "z": "tab_ui", - "name": "trend split (Pump B)", - "func": "const p = msg.payload || {};\nconst flowMsg = p.flowNum != null ? { topic: 'Pump B', payload: Number(p.flowNum) } : null;\nconst powerMsg = p.powerNum != null ? { topic: 'Pump B', payload: Number(p.powerNum) } : null;\nreturn [flowMsg, powerMsg];", - "outputs": 2, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 900, - "y": 1480, - "wires": [ - [ - "trend_short_flow", - "trend_long_flow" - ], - [ - "trend_short_power", - "trend_long_power" - ] - ] - }, { "id": "c_ui_pump_c", "type": "comment", @@ -2661,14 +2739,52 @@ "y": 1840, "wires": [ [ - "ui_pump_c_state", - "ui_pump_c_mode", - "ui_pump_c_ctrl", - "ui_pump_c_flow", - "ui_pump_c_power", - "ui_pump_c_pUp", - "ui_pump_c_pDn", - "trend_split_pump_c" + "dispatch_pump_c" + ] + ] + }, + { + "id": "dispatch_pump_c", + "type": "function", + "z": "tab_ui", + "name": "dispatch Pump C", + "func": "const p = msg.payload || {};\nreturn [\n {payload: String(p.state || 'idle')},\n {payload: String(p.mode || 'auto')},\n {payload: String(p.ctrl || 'n/a')},\n {payload: String(p.flow || 'n/a')},\n {payload: String(p.power || 'n/a')},\n {payload: String(p.pUp || 'n/a')},\n {payload: String(p.pDn || 'n/a')},\n p.flowNum != null ? {topic: 'Pump C', payload: p.flowNum} : null,\n p.powerNum != null ? {topic: 'Pump C', payload: p.powerNum} : null,\n];", + "outputs": 9, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 380, + "y": 1840, + "wires": [ + [ + "ui_pump_c_state" + ], + [ + "ui_pump_c_mode" + ], + [ + "ui_pump_c_ctrl" + ], + [ + "ui_pump_c_flow" + ], + [ + "ui_pump_c_power" + ], + [ + "ui_pump_c_pUp" + ], + [ + "ui_pump_c_pDn" + ], + [ + "trend_short_flow", + "trend_long_flow" + ], + [ + "trend_short_power", + "trend_long_power" ] ] }, @@ -2682,7 +2798,7 @@ "height": "0", "name": "Pump C State", "label": "State", - "format": "{{msg.payload.state}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", @@ -2702,14 +2818,14 @@ "height": "0", "name": "Pump C Mode", "label": "Mode", - "format": "{{msg.payload.mode}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 14, "color": "#000000", "x": 640, - "y": 1870, + "y": 1880, "wires": [] }, { @@ -2722,14 +2838,14 @@ "height": "0", "name": "Pump C Controller %", "label": "Controller %", - "format": "{{msg.payload.ctrl}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 14, "color": "#000000", "x": 640, - "y": 1900, + "y": 1920, "wires": [] }, { @@ -2742,14 +2858,14 @@ "height": "0", "name": "Pump C Flow", "label": "Flow", - "format": "{{msg.payload.flow}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 14, "color": "#000000", "x": 640, - "y": 1930, + "y": 1960, "wires": [] }, { @@ -2762,14 +2878,14 @@ "height": "0", "name": "Pump C Power", "label": "Power", - "format": "{{msg.payload.power}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 14, "color": "#000000", "x": 640, - "y": 1960, + "y": 2000, "wires": [] }, { @@ -2782,14 +2898,14 @@ "height": "0", "name": "Pump C p Upstream", "label": "p Upstream", - "format": "{{msg.payload.pUp}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 14, "color": "#000000", "x": 640, - "y": 1990, + "y": 2040, "wires": [] }, { @@ -2802,14 +2918,14 @@ "height": "0", "name": "Pump C p Downstream", "label": "p Downstream", - "format": "{{msg.payload.pDn}}", + "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 14, "color": "#000000", "x": 640, - "y": 2020, + "y": 2080, "wires": [] }, { @@ -2967,30 +3083,6 @@ "y": 2155, "wires": [] }, - { - "id": "trend_split_pump_c", - "type": "function", - "z": "tab_ui", - "name": "trend split (Pump C)", - "func": "const p = msg.payload || {};\nconst flowMsg = p.flowNum != null ? { topic: 'Pump C', payload: Number(p.flowNum) } : null;\nconst powerMsg = p.powerNum != null ? { topic: 'Pump C', payload: Number(p.powerNum) } : null;\nreturn [flowMsg, powerMsg];", - "outputs": 2, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 900, - "y": 1880, - "wires": [ - [ - "trend_short_flow", - "trend_long_flow" - ], - [ - "trend_short_power", - "trend_long_power" - ] - ] - }, { "id": "c_ui_trends", "type": "comment",