From 1adb1c30f01056583a452df12588bcdf467dc696 Mon Sep 17 00:00:00 2001 From: ykcheungab Date: Mon, 14 Sep 2026 10:26:39 +0800 Subject: [PATCH] =?UTF-8?q?=E6=BB=A4=E6=B3=A2=E9=85=8D=E7=BD=AE=E8=87=AA?= =?UTF-8?q?=E6=8F=8F=E8=BF=B0=EF=BC=9A=E5=8E=BB=E6=8E=89=E8=A6=86=E7=9B=96?= =?UTF-8?q?=E5=BC=8F=E6=80=BB=E5=BC=80=E5=85=B3=EF=BC=8C=E6=AF=8F=E7=BA=A7?= =?UTF-8?q?=E6=8C=89=E8=87=AA=E5=B7=B1=E7=9A=84=E5=80=BC=E7=94=9F=E6=95=88?= =?UTF-8?q?=20-=20What:=20sensor.py=20=E5=88=A0=E9=99=A4=20filters.enabled?= =?UTF-8?q?=20=E8=BF=99=E4=B8=AA=E8=A6=86=E7=9B=96=E5=B1=82=E2=80=94?= =?UTF-8?q?=E2=80=94flow=5Fema=5Falpha=20/=20force=5Fmedian=5Fwindow=20/?= =?UTF-8?q?=20force=5Flowpass=5Fcutoff=5Fhz=20=E7=8E=B0=E5=9C=A8=E5=90=84?= =?UTF-8?q?=E8=87=AA=E6=8C=89=E5=80=BC=E7=94=9F=E6=95=88=EF=BC=880<=CE=B1= 1``, ``force_median_window <= 1`` and + # ``force_lowpass_cutoff_hz <= 0`` each mean "stage off". Values are never + # overridden: there is no master switch that could make the file say 0.5 + # while the device runs 1.0. An invalid value falls back to **off**, never + # to a default that filters. ``enabled`` is kept only as a deprecated + # alias for "turn every stage off" and is honoured when it is explicitly + # false. ``Sensor.filters_state()`` reports what actually took effect. filters_cfg = self._config.get("filters") if not isinstance(filters_cfg, dict): filters_cfg = {} - self._filters_enabled = bool(filters_cfg.get("enabled", True)) + legacy_off = filters_cfg.get("enabled", None) is False + + def _cfg(key, default): + """The configured value, or the documented default when unset.""" + if legacy_off: + return default + value = filters_cfg.get(key, None) + return default if value is None else value self._lowpass_order = 2 self._lowpass_sample_rate_hz = float(DEFAULT_SAMPLE_RATE_HZ) @@ -195,34 +209,31 @@ class Sensor: self._config_fps = float(DEFAULT_SAMPLE_RATE_HZ) self._lowpass_sample_rate_hz = self._config_fps try: - self._lowpass_order = max(1, int(filters_cfg.get("force_lowpass_order", 2))) - rate_cfg = filters_cfg.get("force_lowpass_sample_rate_hz", "auto") - if isinstance(rate_cfg, str): - self._lowpass_rate_auto = rate_cfg.strip().lower() in ("auto", "", "measured") - else: - rate = float(rate_cfg) if rate_cfg is not None else 0.0 - self._lowpass_rate_auto = rate <= 0.0 - if not self._lowpass_rate_auto: - self._lowpass_sample_rate_hz = rate - if self._filters_enabled: - self._lowpass_cutoff_hz = float( - filters_cfg.get("force_lowpass_cutoff_hz", DEFAULT_FORCE_LOWPASS_HZ) - ) + self._lowpass_order = max(1, int(_cfg("force_lowpass_order", 2))) except (TypeError, ValueError): self._lowpass_order = 2 - self._lowpass_sample_rate_hz = self._config_fps - self._lowpass_rate_auto = True - self._lowpass_cutoff_hz = ( - DEFAULT_FORCE_LOWPASS_HZ if self._filters_enabled else 0.0 + rate_cfg = _cfg("force_lowpass_sample_rate_hz", "auto") + if isinstance(rate_cfg, str): + self._lowpass_rate_auto = rate_cfg.strip().lower() in ("auto", "", "measured") + else: + try: + rate = float(rate_cfg) + except (TypeError, ValueError): + rate = 0.0 + self._lowpass_rate_auto = rate <= 0.0 + if not self._lowpass_rate_auto: + self._lowpass_sample_rate_hz = rate + try: + self._lowpass_cutoff_hz = float( + _cfg("force_lowpass_cutoff_hz", DEFAULT_FORCE_LOWPASS_HZ) ) + except (TypeError, ValueError): + self._lowpass_cutoff_hz = 0.0 if self._lowpass_sample_rate_hz <= 0: self._lowpass_sample_rate_hz = self._config_fps + # An unusable cutoff is "off", never a silent jump to the default. if not (0.0 <= self._lowpass_cutoff_hz < 0.5 * self._lowpass_sample_rate_hz): - self._lowpass_cutoff_hz = ( - DEFAULT_FORCE_LOWPASS_HZ - if (self._filters_enabled and 0.0 < DEFAULT_FORCE_LOWPASS_HZ < 0.5 * self._lowpass_sample_rate_hz) - else 0.0 - ) + self._lowpass_cutoff_hz = 0.0 self._lowpass_enabled = self._lowpass_cutoff_hz > 0 self._force_filter = LowpassFilter( cutoff_hz=self._lowpass_cutoff_hz, @@ -234,25 +245,32 @@ class Sensor: self._force_rate = SampleRateEstimator(initial_hz=self._config_fps) self._force_rate_source = "config" - self._force_median_window = 1 - if self._filters_enabled: - try: - self._force_median_window = max( - 1, int(filters_cfg.get("force_median_window", DEFAULT_FORCE_MEDIAN_WINDOW)) - ) - except (TypeError, ValueError): - self._force_median_window = DEFAULT_FORCE_MEDIAN_WINDOW + try: + self._force_median_window = max(1, int(_cfg("force_median_window", DEFAULT_FORCE_MEDIAN_WINDOW))) + except (TypeError, ValueError): + self._force_median_window = 1 self._force_median = CausalMedianFilter(window=self._force_median_window, n_channels=3) self._flow_ema_alpha = 1.0 - if self._filters_enabled: - try: - alpha = float(filters_cfg.get("flow_ema_alpha", DEFAULT_FLOW_EMA_ALPHA)) - except (TypeError, ValueError): - alpha = DEFAULT_FLOW_EMA_ALPHA - if 0.0 < alpha < 1.0: - self._flow_ema_alpha = alpha + try: + alpha = float(_cfg("flow_ema_alpha", DEFAULT_FLOW_EMA_ALPHA)) + except (TypeError, ValueError): + alpha = 1.0 + if 0.0 < alpha < 1.0: + self._flow_ema_alpha = alpha self._ema_flow = None + if legacy_off and self.verbose: + print( + "Note: filters.enabled is deprecated and means 'all stages off'; " + "remove it and switch each stage by its own value " + "(flow_ema_alpha=1, force_median_window=1, force_lowpass_cutoff_hz=0)." + ) + # Derived, for callers that ask "is any stage filtering?". + self._filters_enabled = bool( + self._flow_ema_alpha < 1.0 + or self._force_median_window > 1 + or self._lowpass_enabled + ) if self.verbose: if self.calibration is not None and self.calibration.source_path is not None: