diff --git a/examples/pumpingstation-3pumps-dashboard/build_flow.py b/examples/pumpingstation-3pumps-dashboard/build_flow.py index 3f532f7..18ceecb 100644 --- a/examples/pumpingstation-3pumps-dashboard/build_flow.py +++ b/examples/pumpingstation-3pumps-dashboard/build_flow.py @@ -165,15 +165,31 @@ def dashboard_scaffold(): "groupGap": "12px", "groupBorderRadius": "6px", "widgetGap": "8px", }, } - page = { - "id": "ui_page_ps_demo", "type": "ui-page", - "name": "Pumping Station — 3 Pumps", "ui": "ui_base_ps_demo", + page_control = { + "id": "ui_page_control", "type": "ui-page", + "name": "Control", "ui": "ui_base_ps_demo", "path": "/pumping-station-demo", "icon": "water_pump", "layout": "grid", "theme": "ui_theme_ps_demo", "breakpoints": [{"name": "Default", "px": "0", "cols": "12"}], "order": 1, "className": "", } - return [base, theme, page] + page_short = { + "id": "ui_page_short_trends", "type": "ui-page", + "name": "Trends — 10 min", "ui": "ui_base_ps_demo", + "path": "/pumping-station-demo/trends-short", "icon": "show_chart", + "layout": "grid", "theme": "ui_theme_ps_demo", + "breakpoints": [{"name": "Default", "px": "0", "cols": "12"}], + "order": 2, "className": "", + } + page_long = { + "id": "ui_page_long_trends", "type": "ui-page", + "name": "Trends — 1 hour", "ui": "ui_base_ps_demo", + "path": "/pumping-station-demo/trends-long", "icon": "timeline", + "layout": "grid", "theme": "ui_theme_ps_demo", + "breakpoints": [{"name": "Default", "px": "0", "cols": "12"}], + "order": 3, "className": "", + } + return [base, theme, page_control, page_short, page_long] def ui_group(group_id, name, page_id, width=6, order=1): @@ -241,19 +257,33 @@ def ui_switch(node_id, tab, x, y, group, name, label, on_value, off_value, } -def ui_chart(node_id, tab, x, y, group, name, label, ymin=None, ymax=None): +def ui_chart(node_id, tab, x, y, group, name, label, + width="12", height="6", + remove_older="10", remove_older_unit="60", + remove_older_points="200", + ymin=None, ymax=None, order=1): + """ + FlowFuse ui-chart. Dimensions are in grid units: + width="12" = full group width + height="6" = about 300 px tall + `remove_older` + `remove_older_unit` define the rolling window + (e.g. "10" + "60" = 10 minutes). `remove_older_points` caps points + per series. + """ return { "id": node_id, "type": "ui-chart", "z": tab, "group": group, - "name": name, "label": label, "order": 1, "chartType": "line", + "name": name, "label": label, "order": order, "chartType": "line", "category": "topic", "categoryType": "msg", "xAxisLabel": "", "xAxisType": "time", "xAxisTimeFormat": "auto", "yAxisLabel": "", "ymin": "" if ymin is None else str(ymin), "ymax": "" if ymax is None else str(ymax), "action": "append", "pointShape": "circle", "pointRadius": 2, - "showLegend": True, "removeOlder": "10", "removeOlderUnit": "60", - "removeOlderPoints": "200", "colors": [], "textColor": [], - "textColorDefault": True, - "width": "0", "height": "0", "className": "", + "showLegend": True, + "removeOlder": str(remove_older), + "removeOlderUnit": str(remove_older_unit), + "removeOlderPoints": str(remove_older_points), + "colors": [], "textColor": [], "textColorDefault": True, + "width": str(width), "height": str(height), "className": "", "x": x, "y": y, "wires": [[]], } @@ -615,13 +645,19 @@ def build_ui_tab(): # Dashboard scaffold (page + theme + base) + groups nodes += dashboard_scaffold() - PG = "ui_page_ps_demo" + PG = "ui_page_control" # control page is the main page g_demand = "ui_grp_demand" g_station = "ui_grp_station" g_pump_a = "ui_grp_pump_a" g_pump_b = "ui_grp_pump_b" g_pump_c = "ui_grp_pump_c" - g_trend = "ui_grp_trend" + # Trend groups live on separate pages, not the control page. + PG_SHORT = "ui_page_short_trends" + PG_LONG = "ui_page_long_trends" + g_trend_short_flow = "ui_grp_trend_short_flow" + g_trend_short_power = "ui_grp_trend_short_power" + g_trend_long_flow = "ui_grp_trend_long_flow" + g_trend_long_power = "ui_grp_trend_long_power" g_mgc = "ui_grp_mgc" g_ps = "ui_grp_ps" nodes += [ @@ -632,7 +668,11 @@ def build_ui_tab(): ui_group(g_pump_a, "4a. Pump A", PG, width=4, order=5), ui_group(g_pump_b, "4b. Pump B", PG, width=4, order=6), ui_group(g_pump_c, "4c. Pump C", PG, width=4, order=7), - ui_group(g_trend, "5. Trends", PG, width=12, order=8), + # Trends on separate pages + ui_group(g_trend_short_flow, "Flow (10 min)", PG_SHORT, width=12, order=1), + ui_group(g_trend_short_power, "Power (10 min)", PG_SHORT, width=12, order=2), + ui_group(g_trend_long_flow, "Flow (1 hour)", PG_LONG, width=12, order=1), + ui_group(g_trend_long_power, "Power (1 hour)", PG_LONG, width=12, order=2), ] nodes.append(comment("c_ui_title", TAB_UI, LANE_X[2], 20, @@ -874,9 +914,8 @@ def build_ui_tab(): target_in_ids=[f"lin_seq_{pump}"] )) - # Trend feeder — TWO outputs so flow goes to flow chart, power to - # power chart. Previous bug: single output → both charts saw both - # series and trends were unreadable. + # 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})", @@ -887,22 +926,54 @@ def build_ui_tab(): "{ topic: '" + label + "', payload: Number(p.powerNum) } : null;\n" "return [flowMsg, powerMsg];", outputs=2, - wires=[["trend_chart_flow"], ["trend_chart_power"]] + wires=[ + ["trend_short_flow", "trend_long_flow"], + ["trend_short_power", "trend_long_power"], + ] )) - # Trend charts (shared across all 3 pumps) + # ===== Trend charts — two pages, two charts per page ===== + # Short-term (10 min rolling window) and long-term (1 hour). + # Same data feed; different removeOlder settings. y_charts = y_pumps_start + len(PUMPS) * SECTION_GAP * 2 + 80 nodes.append(comment("c_ui_trends", TAB_UI, LANE_X[2], y_charts, - "── Trends (shared by all pumps) ──", - "Each chart accepts msg.topic as the series name (categoryType=msg)." + "── Trend charts ── (feed to 4 charts on 2 pages)", + "Short-term (10 min) and long-term (1 h) trends share the same feed.\n" + "Each chart on its own page." + )) + # Short-term (10 min) + nodes.append(ui_chart( + "trend_short_flow", TAB_UI, LANE_X[3], y_charts + 40, + g_trend_short_flow, + "Flow per pump — 10 min", "Flow per pump (m³/h)", + width="12", height="8", + remove_older="10", remove_older_unit="60", remove_older_points="300", + order=1, )) nodes.append(ui_chart( - "trend_chart_flow", TAB_UI, LANE_X[3], y_charts + 40, g_trend, - "Flow per pump (m³/h)", "Flow per pump" + "trend_short_power", TAB_UI, LANE_X[3], y_charts + 120, + g_trend_short_power, + "Power per pump — 10 min", "Power per pump (kW)", + width="12", height="8", + remove_older="10", remove_older_unit="60", remove_older_points="300", + order=1, + )) + # Long-term (1 hour) + nodes.append(ui_chart( + "trend_long_flow", TAB_UI, LANE_X[3], y_charts + 200, + g_trend_long_flow, + "Flow per pump — 1 hour", "Flow per pump (m³/h)", + width="12", height="8", + remove_older="60", remove_older_unit="60", remove_older_points="1800", + order=1, )) nodes.append(ui_chart( - "trend_chart_power", TAB_UI, LANE_X[3], y_charts + 100, g_trend, - "Power per pump (kW)", "Power per pump" + "trend_long_power", TAB_UI, LANE_X[3], y_charts + 280, + g_trend_long_power, + "Power per pump — 1 hour", "Power per pump (kW)", + width="12", height="8", + remove_older="60", remove_older_unit="60", remove_older_points="1800", + order=1, )) return nodes diff --git a/examples/pumpingstation-3pumps-dashboard/flow.json b/examples/pumpingstation-3pumps-dashboard/flow.json index 4156ab7..b3e7ade 100644 --- a/examples/pumpingstation-3pumps-dashboard/flow.json +++ b/examples/pumpingstation-3pumps-dashboard/flow.json @@ -1126,9 +1126,9 @@ } }, { - "id": "ui_page_ps_demo", + "id": "ui_page_control", "type": "ui-page", - "name": "Pumping Station \u2014 3 Pumps", + "name": "Control", "ui": "ui_base_ps_demo", "path": "/pumping-station-demo", "icon": "water_pump", @@ -1144,11 +1144,49 @@ "order": 1, "className": "" }, + { + "id": "ui_page_short_trends", + "type": "ui-page", + "name": "Trends \u2014 10 min", + "ui": "ui_base_ps_demo", + "path": "/pumping-station-demo/trends-short", + "icon": "show_chart", + "layout": "grid", + "theme": "ui_theme_ps_demo", + "breakpoints": [ + { + "name": "Default", + "px": "0", + "cols": "12" + } + ], + "order": 2, + "className": "" + }, + { + "id": "ui_page_long_trends", + "type": "ui-page", + "name": "Trends \u2014 1 hour", + "ui": "ui_base_ps_demo", + "path": "/pumping-station-demo/trends-long", + "icon": "timeline", + "layout": "grid", + "theme": "ui_theme_ps_demo", + "breakpoints": [ + { + "name": "Default", + "px": "0", + "cols": "12" + } + ], + "order": 3, + "className": "" + }, { "id": "ui_grp_demand", "type": "ui-group", "name": "1. Process Demand", - "page": "ui_page_ps_demo", + "page": "ui_page_control", "width": "12", "height": "1", "order": 1, @@ -1162,7 +1200,7 @@ "id": "ui_grp_station", "type": "ui-group", "name": "2. Station Controls", - "page": "ui_page_ps_demo", + "page": "ui_page_control", "width": "12", "height": "1", "order": 2, @@ -1176,7 +1214,7 @@ "id": "ui_grp_mgc", "type": "ui-group", "name": "3a. MGC Status", - "page": "ui_page_ps_demo", + "page": "ui_page_control", "width": "6", "height": "1", "order": 3, @@ -1190,7 +1228,7 @@ "id": "ui_grp_ps", "type": "ui-group", "name": "3b. Basin Status", - "page": "ui_page_ps_demo", + "page": "ui_page_control", "width": "6", "height": "1", "order": 4, @@ -1204,7 +1242,7 @@ "id": "ui_grp_pump_a", "type": "ui-group", "name": "4a. Pump A", - "page": "ui_page_ps_demo", + "page": "ui_page_control", "width": "4", "height": "1", "order": 5, @@ -1218,7 +1256,7 @@ "id": "ui_grp_pump_b", "type": "ui-group", "name": "4b. Pump B", - "page": "ui_page_ps_demo", + "page": "ui_page_control", "width": "4", "height": "1", "order": 6, @@ -1232,7 +1270,7 @@ "id": "ui_grp_pump_c", "type": "ui-group", "name": "4c. Pump C", - "page": "ui_page_ps_demo", + "page": "ui_page_control", "width": "4", "height": "1", "order": 7, @@ -1243,13 +1281,55 @@ "visible": true }, { - "id": "ui_grp_trend", + "id": "ui_grp_trend_short_flow", "type": "ui-group", - "name": "5. Trends", - "page": "ui_page_ps_demo", + "name": "Flow (10 min)", + "page": "ui_page_short_trends", "width": "12", "height": "1", - "order": 8, + "order": 1, + "showTitle": true, + "className": "", + "groupType": "default", + "disabled": false, + "visible": true + }, + { + "id": "ui_grp_trend_short_power", + "type": "ui-group", + "name": "Power (10 min)", + "page": "ui_page_short_trends", + "width": "12", + "height": "1", + "order": 2, + "showTitle": true, + "className": "", + "groupType": "default", + "disabled": false, + "visible": true + }, + { + "id": "ui_grp_trend_long_flow", + "type": "ui-group", + "name": "Flow (1 hour)", + "page": "ui_page_long_trends", + "width": "12", + "height": "1", + "order": 1, + "showTitle": true, + "className": "", + "groupType": "default", + "disabled": false, + "visible": true + }, + { + "id": "ui_grp_trend_long_power", + "type": "ui-group", + "name": "Power (1 hour)", + "page": "ui_page_long_trends", + "width": "12", + "height": "1", + "order": 2, "showTitle": true, "className": "", "groupType": "default", @@ -2198,10 +2278,12 @@ "y": 1080, "wires": [ [ - "trend_chart_flow" + "trend_short_flow", + "trend_long_flow" ], [ - "trend_chart_power" + "trend_short_power", + "trend_long_power" ] ] }, @@ -2548,10 +2630,12 @@ "y": 1480, "wires": [ [ - "trend_chart_flow" + "trend_short_flow", + "trend_long_flow" ], [ - "trend_chart_power" + "trend_short_power", + "trend_long_power" ] ] }, @@ -2898,10 +2982,12 @@ "y": 1880, "wires": [ [ - "trend_chart_flow" + "trend_short_flow", + "trend_long_flow" ], [ - "trend_chart_power" + "trend_short_power", + "trend_long_power" ] ] }, @@ -2909,19 +2995,19 @@ "id": "c_ui_trends", "type": "comment", "z": "tab_ui", - "name": "\u2500\u2500 Trends (shared by all pumps) \u2500\u2500", - "info": "Each chart accepts msg.topic as the series name (categoryType=msg).", + "name": "\u2500\u2500 Trend charts \u2500\u2500 (feed to 4 charts on 2 pages)", + "info": "Short-term (10 min) and long-term (1 h) trends share the same feed.\nEach chart on its own page.", "x": 640, "y": 2280, "wires": [] }, { - "id": "trend_chart_flow", + "id": "trend_short_flow", "type": "ui-chart", "z": "tab_ui", - "group": "ui_grp_trend", - "name": "Flow per pump (m\u00b3/h)", - "label": "Flow per pump", + "group": "ui_grp_trend_short_flow", + "name": "Flow per pump \u2014 10 min", + "label": "Flow per pump (m\u00b3/h)", "order": 1, "chartType": "line", "category": "topic", @@ -2938,12 +3024,12 @@ "showLegend": true, "removeOlder": "10", "removeOlderUnit": "60", - "removeOlderPoints": "200", + "removeOlderPoints": "300", "colors": [], "textColor": [], "textColorDefault": true, - "width": "0", - "height": "0", + "width": "12", + "height": "8", "className": "", "x": 900, "y": 2320, @@ -2952,12 +3038,12 @@ ] }, { - "id": "trend_chart_power", + "id": "trend_short_power", "type": "ui-chart", "z": "tab_ui", - "group": "ui_grp_trend", - "name": "Power per pump (kW)", - "label": "Power per pump", + "group": "ui_grp_trend_short_power", + "name": "Power per pump \u2014 10 min", + "label": "Power per pump (kW)", "order": 1, "chartType": "line", "category": "topic", @@ -2974,15 +3060,87 @@ "showLegend": true, "removeOlder": "10", "removeOlderUnit": "60", - "removeOlderPoints": "200", + "removeOlderPoints": "300", "colors": [], "textColor": [], "textColorDefault": true, - "width": "0", - "height": "0", + "width": "12", + "height": "8", "className": "", "x": 900, - "y": 2380, + "y": 2400, + "wires": [ + [] + ] + }, + { + "id": "trend_long_flow", + "type": "ui-chart", + "z": "tab_ui", + "group": "ui_grp_trend_long_flow", + "name": "Flow per pump \u2014 1 hour", + "label": "Flow per pump (m\u00b3/h)", + "order": 1, + "chartType": "line", + "category": "topic", + "categoryType": "msg", + "xAxisLabel": "", + "xAxisType": "time", + "xAxisTimeFormat": "auto", + "yAxisLabel": "", + "ymin": "", + "ymax": "", + "action": "append", + "pointShape": "circle", + "pointRadius": 2, + "showLegend": true, + "removeOlder": "60", + "removeOlderUnit": "60", + "removeOlderPoints": "1800", + "colors": [], + "textColor": [], + "textColorDefault": true, + "width": "12", + "height": "8", + "className": "", + "x": 900, + "y": 2480, + "wires": [ + [] + ] + }, + { + "id": "trend_long_power", + "type": "ui-chart", + "z": "tab_ui", + "group": "ui_grp_trend_long_power", + "name": "Power per pump \u2014 1 hour", + "label": "Power per pump (kW)", + "order": 1, + "chartType": "line", + "category": "topic", + "categoryType": "msg", + "xAxisLabel": "", + "xAxisType": "time", + "xAxisTimeFormat": "auto", + "yAxisLabel": "", + "ymin": "", + "ymax": "", + "action": "append", + "pointShape": "circle", + "pointRadius": 2, + "showLegend": true, + "removeOlder": "60", + "removeOlderUnit": "60", + "removeOlderPoints": "1800", + "colors": [], + "textColor": [], + "textColorDefault": true, + "width": "12", + "height": "8", + "className": "", + "x": 900, + "y": 2560, "wires": [ [] ]