From 85865093d432eb994251689fa8662837d31f5614 Mon Sep 17 00:00:00 2001 From: Pedro Lamas Date: Fri, 22 May 2026 16:22:26 +0100 Subject: [PATCH] feat: adds diagnostics watches support Signed-off-by: Pedro Lamas --- .../DiagnosticsCardConfigDialog.vue | 65 +++++++++-- ...sticsCard.vue => DiagnosticsChartCard.vue} | 7 +- .../diagnostics/DiagnosticsWatchCard.vue | 89 ++++++++++++++ .../diagnostics/config/CardConfigStep.vue | 42 ++++--- ...ConfigStep.vue => ChartAxisConfigStep.vue} | 6 +- ...figStep.vue => ChartMetricsConfigStep.vue} | 10 +- .../config/MetricsCollectorConfig.vue | 110 ++++++++++-------- .../config/WatchMetricsConfigStep.vue | 95 +++++++++++++++ src/locales/en.yaml | 8 ++ src/store/diagnostics/actions.ts | 9 ++ src/store/diagnostics/index.ts | 13 +++ src/store/diagnostics/mutations.ts | 13 +++ src/store/diagnostics/state.ts | 7 ++ src/store/diagnostics/types.ts | 31 ++++- src/store/index.ts | 4 +- src/store/layout/mutations.ts | 4 + src/store/layout/state.ts | 109 ++++++++++------- src/store/printer/actions.ts | 36 ++++-- src/store/types.ts | 4 +- src/views/Diagnostics.vue | 65 +++++++++-- 20 files changed, 578 insertions(+), 149 deletions(-) rename src/components/widgets/diagnostics/{DiagnosticsCard.vue => DiagnosticsChartCard.vue} (97%) create mode 100644 src/components/widgets/diagnostics/DiagnosticsWatchCard.vue rename src/components/widgets/diagnostics/config/{AxesConfigStep.vue => ChartAxisConfigStep.vue} (94%) rename src/components/widgets/diagnostics/config/{MetricsConfigStep.vue => ChartMetricsConfigStep.vue} (94%) create mode 100644 src/components/widgets/diagnostics/config/WatchMetricsConfigStep.vue create mode 100644 src/store/diagnostics/actions.ts create mode 100644 src/store/diagnostics/index.ts create mode 100644 src/store/diagnostics/mutations.ts create mode 100644 src/store/diagnostics/state.ts diff --git a/src/components/widgets/diagnostics/DiagnosticsCardConfigDialog.vue b/src/components/widgets/diagnostics/DiagnosticsCardConfigDialog.vue index 3f00860430..2e5e66b833 100644 --- a/src/components/widgets/diagnostics/DiagnosticsCardConfigDialog.vue +++ b/src/components/widgets/diagnostics/DiagnosticsCardConfigDialog.vue @@ -1,7 +1,7 @@ @@ -73,6 +75,16 @@ export default class CardConfigStep extends Vue { @Prop({ type: Object, required: true }) readonly config!: DiagnosticsCardConfig + get height (): number { + return this.config.type === 'chart' ? this.config.height : 0 + } + + set height (value: number) { + if (this.config.type === 'chart') { + this.config.height = value + } + } + get icons () { const icons = Object.keys(Icons) return icons.sort().map(icon => ({ text: icon, value: icon })) diff --git a/src/components/widgets/diagnostics/config/AxesConfigStep.vue b/src/components/widgets/diagnostics/config/ChartAxisConfigStep.vue similarity index 94% rename from src/components/widgets/diagnostics/config/AxesConfigStep.vue rename to src/components/widgets/diagnostics/config/ChartAxisConfigStep.vue index 43e8da87f7..5ee0620b61 100644 --- a/src/components/widgets/diagnostics/config/AxesConfigStep.vue +++ b/src/components/widgets/diagnostics/config/ChartAxisConfigStep.vue @@ -99,12 +99,12 @@ + + diff --git a/src/components/widgets/diagnostics/config/WatchMetricsConfigStep.vue b/src/components/widgets/diagnostics/config/WatchMetricsConfigStep.vue new file mode 100644 index 0000000000..134e5c8406 --- /dev/null +++ b/src/components/widgets/diagnostics/config/WatchMetricsConfigStep.vue @@ -0,0 +1,95 @@ + + + diff --git a/src/locales/en.yaml b/src/locales/en.yaml index dd9d0135f5..17707b1ea7 100644 --- a/src/locales/en.yaml +++ b/src/locales/en.yaml @@ -392,6 +392,7 @@ app: name: Name new_password: New password no_notifications: No notifications + no_watch_metrics: No watch metrics configured 'on': 'On' 'off': 'Off' numeric_prefix_sort: Numeric Prefix sort @@ -555,6 +556,8 @@ app: user: User title: add_chart: Add Chart + add_watch_panel: Add Watch Panel + add_watches: Add Watches add_printer: Add Printer beacon: Beacon bedmesh: Bed Mesh @@ -565,6 +568,7 @@ app: console: Console diagnostics: Diagnostics edit_chart: Edit Chart + edit_watch_panel: Edit Watch Panel endstops: Endstops fans_outputs: Fans & Outputs gcode_preview: Gcode Preview @@ -696,6 +700,7 @@ app: camera_url_snapshot: Camera Url Snapshot camera_url_stream: Camera Url Stream card: Card + chart: Chart collector: Collector confirm_on_estop: Require confirm on Emergency Stop confirm_on_power_device_change: Require confirm on Device Power changes @@ -789,6 +794,9 @@ app: toolhead_z_move_distances: Toolhead Z distance values type: Type unit: Unit + value: Value + watch_metrics: Watch Metrics + watch_panel: Watch Panel warn_on_cpu_throttled: Warn if CPU throttling is detected warn_on_stepper_driver_overheating: Warn if stepper driver is over-heating z_adjust_values: Z Adjust values diff --git a/src/store/diagnostics/actions.ts b/src/store/diagnostics/actions.ts new file mode 100644 index 0000000000..a1cc71d6e9 --- /dev/null +++ b/src/store/diagnostics/actions.ts @@ -0,0 +1,9 @@ +import type { ActionTree } from 'vuex' +import type { RootState } from '../types' +import type { DiagnosticsState } from './types' + +export const actions = { + async reset ({ commit }) { + commit('setReset') + } +} satisfies ActionTree diff --git a/src/store/diagnostics/index.ts b/src/store/diagnostics/index.ts new file mode 100644 index 0000000000..d9dba37bbe --- /dev/null +++ b/src/store/diagnostics/index.ts @@ -0,0 +1,13 @@ +import type { Module } from 'vuex' +import type { RootState } from '../types' +import { state } from './state' +import { mutations } from './mutations' +import { actions } from './actions' +import type { DiagnosticsState } from './types' + +export const diagnostics = { + namespaced: true, + state, + mutations, + actions +} satisfies Module diff --git a/src/store/diagnostics/mutations.ts b/src/store/diagnostics/mutations.ts new file mode 100644 index 0000000000..556a7d40fd --- /dev/null +++ b/src/store/diagnostics/mutations.ts @@ -0,0 +1,13 @@ +import type { MutationTree } from 'vuex' +import { defaultState } from './state' +import type { DiagnosticsState } from './types' + +export const mutations = { + setReset (state: DiagnosticsState) { + Object.assign(state, defaultState()) + }, + + setWatchValues (state: DiagnosticsState, values: Record) { + state.watchValues = values + } +} satisfies MutationTree diff --git a/src/store/diagnostics/state.ts b/src/store/diagnostics/state.ts new file mode 100644 index 0000000000..2f5a6ab596 --- /dev/null +++ b/src/store/diagnostics/state.ts @@ -0,0 +1,7 @@ +import type { DiagnosticsState } from './types' + +export const defaultState = (): DiagnosticsState => ({ + watchValues: {} +}) + +export const state = defaultState() diff --git a/src/store/diagnostics/types.ts b/src/store/diagnostics/types.ts index 70e38c991d..02087dc47f 100644 --- a/src/store/diagnostics/types.ts +++ b/src/store/diagnostics/types.ts @@ -1,17 +1,36 @@ import type { LayoutConfig } from '@/store/layout/types' +export interface DiagnosticsState { + watchValues: Record +} + export interface DiagnosticsCardContainer { [key: string]: DiagnosticsCardConfig[] } -export interface DiagnosticsCardConfig extends LayoutConfig { +interface DiagnosticsCardBase extends LayoutConfig { icon: string title: string - height: number +} +export interface DiagnosticsChartConfig extends DiagnosticsCardBase { + type: 'chart' + height: number axes: ChartAxis[] } +export interface DiagnosticsWatchesConfig extends DiagnosticsCardBase { + type: 'watches' + metrics: WatchMetric[] +} + +export type DiagnosticsCardConfig = DiagnosticsChartConfig | DiagnosticsWatchesConfig + +export interface WatchMetric { + name: string + collector: string +} + export interface ChartAxis { enabled: boolean unit: string @@ -19,16 +38,16 @@ export interface ChartAxis { max?: number showLegend: boolean - metrics: Metric[] + metrics: ChartMetric[] } -export interface Metric { +export interface ChartMetric { collector: string name: string - style: MetricStyle + style: ChartMetricStyle } -export interface MetricStyle { +export interface ChartMetricStyle { lineColor: string lineStyle: 'solid' | 'dashed' | 'dotted' fillColor: string | null diff --git a/src/store/index.ts b/src/store/index.ts index b8f55cbfb7..efeb87f85b 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -31,6 +31,7 @@ import { sensors } from './sensors' import { database } from './database' import { analysis } from './analysis' import { afc } from './afc' +import { diagnostics } from './diagnostics' Vue.use(Vuex) @@ -63,7 +64,8 @@ export const storeOptions = { sensors, database, analysis, - afc + afc, + diagnostics } satisfies RootModules, mutations: {}, actions: { diff --git a/src/store/layout/mutations.ts b/src/store/layout/mutations.ts index f85813dd9e..4e409458aa 100644 --- a/src/store/layout/mutations.ts +++ b/src/store/layout/mutations.ts @@ -63,6 +63,10 @@ export const mutations = { for (const diagnosticsCardConfigs of Object.values(diagnostics)) { for (const diagnosticsCardConfig of diagnosticsCardConfigs) { + if (diagnosticsCardConfig.type !== 'chart') { + continue + } + for (const axis of diagnosticsCardConfig.axes) { for (const metric of axis.metrics) { metric.style.fillColor = metric.style.fillColor ?? null diff --git a/src/store/layout/state.ts b/src/store/layout/state.ts index 2a1276a16a..837f1f330a 100644 --- a/src/store/layout/state.ts +++ b/src/store/layout/state.ts @@ -1,6 +1,6 @@ import type { LayoutState } from './types' import { v4 as uuidv4 } from 'uuid' -import type { DiagnosticsCardContainer } from '@/store/diagnostics/types' +import type { DiagnosticsChartConfig, DiagnosticsWatchesConfig } from '@/store/diagnostics/types' /** * Maintains the state of our page layouts. @@ -9,6 +9,70 @@ import type { DiagnosticsCardContainer } from '@/store/diagnostics/types' * - If card is collapsed or not. * - If card is enabled or not. */ + +export const defaultDiagnosticsChartConfig = (): DiagnosticsChartConfig => { + return { + id: uuidv4(), + enabled: true, + type: 'chart', + title: 'Speeds', + collapsed: false, + height: 300, + icon: 'motion', + axes: [ + { + enabled: true, + unit: 'mm/s', + showLegend: true, + metrics: [ + { + collector: 'printer.motion_report.live_velocity', + name: 'Velocity', + style: { lineStyle: 'solid', lineColor: '#2196f3', fillColor: null, fillOpacity: 0, displayLegend: true } + }, { + collector: 'printer.toolhead.max_velocity', + name: 'Max Velocity', + style: { lineStyle: 'dotted', lineColor: '#0075d2', fillColor: null, fillOpacity: 0, displayLegend: false } + } + ] + }, { + enabled: true, + unit: 'mm³/s', + showLegend: true, + max: 20, + metrics: [ + { + collector: 'printer.motion_report.live_extruder_velocity * Math.PI * (printer.configfile.settings.extruder.filament_diameter / 2) ** 2', + name: 'Flow', + style: { lineStyle: 'solid', lineColor: '#b12f36', fillColor: null, fillOpacity: 5, displayLegend: true } + }, { + collector: '12', + name: 'Max Flow', + style: { lineStyle: 'dashed', lineColor: '#820007', fillColor: null, fillOpacity: 0, displayLegend: false } + } + ] + } + ] + } +} + +export const defaultDiagnosticsWatchesConfig = (): DiagnosticsWatchesConfig => { + return { + id: uuidv4(), + enabled: true, + collapsed: false, + type: 'watches', + title: 'Watches', + icon: 'magnify', + metrics: [ + { + name: 'CPU', + collector: 'printer.system_stats.cputime' + } + ] + } +} + export const defaultState = (): LayoutState => { return { layouts: { @@ -38,44 +102,11 @@ export const defaultState = (): LayoutState => { ] }, diagnostics: { - container1: [{ - id: uuidv4(), - enabled: true, - title: 'Speeds', - collapsed: false, - height: 300, - icon: 'motion', - axes: [{ - enabled: true, - unit: 'mm/s', - showLegend: true, - metrics: [{ - collector: 'printer.motion_report.live_velocity', - name: 'Velocity', - style: { lineStyle: 'solid', lineColor: '#2196f3', fillColor: null, fillOpacity: 0, displayLegend: true } - }, { - collector: 'printer.toolhead.max_velocity', - name: 'Max Velocity', - style: { lineStyle: 'dotted', lineColor: '#0075d2', fillColor: null, fillOpacity: 0, displayLegend: false } - }] - }, { - enabled: true, - unit: 'mm³/s', - showLegend: true, - max: 20, - metrics: [{ - collector: 'printer.motion_report.live_extruder_velocity * Math.PI * ' + - '(printer.configfile.settings.extruder.filament_diameter / 2) ** 2', - name: 'Flow', - style: { lineStyle: 'solid', lineColor: '#b12f36', fillColor: null, fillOpacity: 5, displayLegend: true } - }, { - collector: '12', - name: 'Max Flow', - style: { lineStyle: 'dashed', lineColor: '#820007', fillColor: null, fillOpacity: 0, displayLegend: false } - }] - }] - }] - } as DiagnosticsCardContainer + container1: [ + defaultDiagnosticsChartConfig(), + defaultDiagnosticsWatchesConfig() + ] + } } } } diff --git a/src/store/printer/actions.ts b/src/store/printer/actions.ts index 0dbbcf7fea..ed196faf70 100644 --- a/src/store/printer/actions.ts +++ b/src/store/printer/actions.ts @@ -279,23 +279,39 @@ export const actions = { } const layout = rootState.layout.layouts.diagnostics as DiagnosticsCardContainer - - const metrics = Object.values(layout) + const allCards = Object.values(layout) .flat() + + const chartMetrics = allCards + .filter(card => card.type === 'chart') .flatMap(card => card.axes) .filter(axis => axis.enabled) .flatMap(axis => axis.metrics) - const collectors = [...new Set(metrics.map(metric => metric.collector))] + if (chartMetrics.length > 0) { + const collectors = [...new Set(chartMetrics.map(metric => metric.collector))] + + const data = await evalCollectors(state.printer, collectors) - const data = await evalCollectors(state.printer, collectors) + data.date = new Date() - data.date = new Date() + commit('charts/setChartEntry', { + type: 'diagnostics', + retention: rootGetters['charts/getChartRetention'], + data + }, { root: true }) + } - commit('charts/setChartEntry', { - type: 'diagnostics', - retention: rootGetters['charts/getChartRetention'], - data - }, { root: true }) + const watchMetrics = allCards + .filter(card => card.type === 'watches') + .flatMap(card => card.metrics) + + if (watchMetrics.length > 0) { + const collectors = [...new Set(watchMetrics.map(metric => metric.collector))] + + const data = await evalCollectors(state.printer, collectors) + + commit('diagnostics/setWatchValues', data, { root: true }) + } } } satisfies ActionTree diff --git a/src/store/types.ts b/src/store/types.ts index f45f95cd3e..3d78b3db6e 100644 --- a/src/store/types.ts +++ b/src/store/types.ts @@ -26,6 +26,7 @@ import type { sensors } from './sensors' import type { database } from './database' import type { analysis } from './analysis' import type { afc } from './afc' +import type { diagnostics } from './diagnostics' import type { storeOptions } from '.' type RootModulesType = { @@ -55,7 +56,8 @@ type RootModulesType = { sensors: typeof sensors, database: typeof database, analysis: typeof analysis, - afc: typeof afc + afc: typeof afc, + diagnostics: typeof diagnostics } type RootStateType = { diff --git a/src/views/Diagnostics.vue b/src/views/Diagnostics.vue index 2bd0aaa7c6..c2356ded06 100644 --- a/src/views/Diagnostics.vue +++ b/src/views/Diagnostics.vue @@ -10,6 +10,7 @@ {{ $t('app.general.title.add_chart') }} + + + $plus + + {{ $t('app.general.title.add_watches') }} + @@ -46,8 +60,15 @@ @end.stop="updateLayout" >