From 1518f4aae328c124f9da390a95f35ead53433185 Mon Sep 17 00:00:00 2001 From: Oliver Bagge Date: Tue, 25 Aug 2026 13:43:11 +0200 Subject: [PATCH] fix(filters): stabilize filters, use reported sample rate, support comma values Prefer the device-reported sensor frequency when deriving live chart filter bounds, falling back to the selected UI value only when no reported value is available. This keeps cutoff/notch limits aligned with the frequency actually applied on the device and avoids stale local selections. Also guard IIR filter stages against non-finite output so NaN/Infinity values do not reach the chart, and accept comma decimal input without rewriting the field text while the user is editing. --- .../widgets/sensors/values/sensor_chart.dart | 16 +++++----- .../axis_configuration_sheet.dart | 30 ++++++++++++++----- .../sensor_chart/axis_filter_engine.dart | 12 ++++++++ 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/open_wearable/lib/widgets/sensors/values/sensor_chart.dart b/open_wearable/lib/widgets/sensors/values/sensor_chart.dart index f9abdf3a..20a460b4 100644 --- a/open_wearable/lib/widgets/sensors/values/sensor_chart.dart +++ b/open_wearable/lib/widgets/sensors/values/sensor_chart.dart @@ -641,14 +641,6 @@ class _SensorChartState extends State { SensorConfiguration configuration, SensorConfigurationProvider? sensorConfigurationProvider, ) { - final selectedValue = - sensorConfigurationProvider?.getSelectedConfigurationValue( - configuration, - ); - if (selectedValue is SensorFrequencyConfigurationValue) { - return selectedValue; - } - final reportedValue = sensorConfigurationProvider?.getLastReportedConfigurationValue( configuration, @@ -657,6 +649,14 @@ class _SensorChartState extends State { return reportedValue; } + final selectedValue = + sensorConfigurationProvider?.getSelectedConfigurationValue( + configuration, + ); + if (selectedValue is SensorFrequencyConfigurationValue) { + return selectedValue; + } + final dynamic configurationDynamic = configuration; try { final currentValue = configurationDynamic.currentValue; diff --git a/open_wearable/lib/widgets/sensors/values/sensor_chart/axis_configuration_sheet.dart b/open_wearable/lib/widgets/sensors/values/sensor_chart/axis_configuration_sheet.dart index f2827141..043bce00 100644 --- a/open_wearable/lib/widgets/sensors/values/sensor_chart/axis_configuration_sheet.dart +++ b/open_wearable/lib/widgets/sensors/values/sensor_chart/axis_configuration_sheet.dart @@ -328,7 +328,10 @@ class _SingleCutoffFieldsState extends State<_SingleCutoffFields> { void didUpdateWidget(covariant _SingleCutoffFields oldWidget) { super.didUpdateWidget(oldWidget); if (widget.frequencyHz != oldWidget.frequencyHz) { - _replaceText(_frequencyController, _formatNumber(widget.frequencyHz)); + _replaceFrequencyTextIfChanged( + _frequencyController, + widget.frequencyHz, + ); } if (widget.order != oldWidget.order) { _replaceText(_orderController, widget.order.toString()); @@ -373,7 +376,7 @@ class _SingleCutoffFieldsState extends State<_SingleCutoffFields> { return; } widget.onChanged( - double.parse(_frequencyController.text), + _parseFrequency(_frequencyController.text)!, int.parse(_orderController.text), ); } @@ -434,10 +437,10 @@ class _NotchFieldsState extends State<_NotchFields> { void didUpdateWidget(covariant _NotchFields oldWidget) { super.didUpdateWidget(oldWidget); if (widget.centerHz != oldWidget.centerHz) { - _replaceText(_centerController, _formatNumber(widget.centerHz)); + _replaceFrequencyTextIfChanged(_centerController, widget.centerHz); } if (widget.widthHz != oldWidget.widthHz) { - _replaceText(_widthController, _formatNumber(widget.widthHz)); + _replaceFrequencyTextIfChanged(_widthController, widget.widthHz); } if (widget.order != oldWidget.order) { _replaceText(_orderController, widget.order.toString()); @@ -493,8 +496,8 @@ class _NotchFieldsState extends State<_NotchFields> { return; } widget.onChanged( - double.parse(_centerController.text), - double.parse(_widthController.text), + _parseFrequency(_centerController.text)!, + _parseFrequency(_widthController.text)!, int.parse(_orderController.text), ); } @@ -566,7 +569,7 @@ class _FrequencyInputField extends StatelessWidget { controller: controller, keyboardType: const TextInputType.numberWithOptions(decimal: true), inputFormatters: [ - FilteringTextInputFormatter.allow(RegExp(r'[0-9.]')), + FilteringTextInputFormatter.allow(RegExp(r'[0-9.,]')), ], decoration: _filterInputDecoration( context, @@ -690,13 +693,24 @@ String? _validateNotchOrder(String? value) { } double? _parseFrequency(String? value) { - final trimmed = value?.trim(); + final trimmed = value?.trim().replaceAll(',', '.'); if (trimmed == null || trimmed.isEmpty) { return null; } return double.tryParse(trimmed); } +void _replaceFrequencyTextIfChanged( + TextEditingController controller, + double value, +) { + final parsed = _parseFrequency(controller.text); + if (parsed != null && (parsed - value).abs() < 1e-9) { + return; + } + _replaceText(controller, _formatNumber(value)); +} + void _replaceText(TextEditingController controller, String value) { if (controller.text == value) { return; diff --git a/open_wearable/lib/widgets/sensors/values/sensor_chart/axis_filter_engine.dart b/open_wearable/lib/widgets/sensors/values/sensor_chart/axis_filter_engine.dart index fe364c19..45af7039 100644 --- a/open_wearable/lib/widgets/sensors/values/sensor_chart/axis_filter_engine.dart +++ b/open_wearable/lib/widgets/sensors/values/sensor_chart/axis_filter_engine.dart @@ -34,6 +34,10 @@ class _ButterworthFirstOrderStage implements _IirFilterStage { ); final output = coefficients.b0 * input + coefficients.b1 * _x1 - coefficients.a1 * _y1; + if (!output.isFinite) { + reset(); + return _prime(input); + } _x1 = input; _y1 = output; return output; @@ -88,6 +92,10 @@ class _ButterworthBiquadStage implements _IirFilterStage { coefficients.b2 * _x2 - coefficients.a1 * _y1 - coefficients.a2 * _y2; + if (!output.isFinite) { + reset(); + return _prime(input); + } _x2 = _x1; _x1 = input; _y2 = _y1; @@ -146,6 +154,10 @@ class _NotchBiquadStage implements _IirFilterStage { coefficients.b2 * _x2 - coefficients.a1 * _y1 - coefficients.a2 * _y2; + if (!output.isFinite) { + reset(); + return _prime(input); + } _x2 = _x1; _x1 = input; _y2 = _y1;