Automation

Following an ESPHome Reading Through Its Filter Pipeline

An ESPHome sensor’s raw reading is not necessarily the value that reaches publication. Between acquisition and publication, ESPHome can process the reading through a filters: chain. A filter may transform the value, produce an output based on several readings, or prevent the current value from continuing.

That distinction explains why filtered state may differ from raw state—and why every raw reading does not necessarily produce a published update.

Scope: This is a documentation-based explanation. No hardware, timing, filter-performance, or end-to-end delivery test was performed.

The path from reading to publication

A useful conceptual model is:

text example; exact text follows
Illustrative example from this article; not a live system capture.
Raw sensor reading
        |
        v
     Filter
        |
        | returns a value
        v
   Next filter
        |
        +---- returns empty optional: stop
        |
        v
Published filtered state
        |
        +---- ESPHome API --> Home Assistant
        |
        +---- MQTT, if configured

ESPHome describes sensor filters as a way to preprocess values before sending them to Home Assistant. Averaging recent readings is one documented use case (Sensor Component).

The API represents this process as a chain of filter objects. Its base Filter class holds a pointer to the next filter, while each implementation receives values through new_value() (Filter class reference).

This linked structure supports reading a filters: list as a sequence in which one stage’s output becomes the next stage’s input. However, the supplied documentation does not separately state in prose that YAML declaration order is execution order. The ordered trace below is therefore an inference from the configuration list and linked-chain API, not a directly quoted guarantee.

An annotated filter list

ESPHome declares sensor filters as entries under filters:. This shortened example uses settings shown in the official documentation:

filters:
  - offset: 2.0                  # per-value transform
  - multiply: !lambda return 1.2; # per-value transform
  - median:                     # windowed filter
      window_size: 5
      send_every: 5
      send_first_at: 1
  - sliding_window_moving_average: # windowed filter
      window_size: 15
      send_every: 15
  - throttle: 1s                # rate gate

These stages do different kinds of work:

  • offset adds a configured value to each sensor value.
  • multiply multiplies each value by a configured value.
  • median calculates a moving median over recent values.
  • sliding_window_moving_average applies a moving average over a sliding window.
  • throttle can withhold a value when the configured interval has not elapsed.

Those descriptions and settings come from the official sensor filter documentation and filter API overview. This is an explanatory combination of documented filters, not a recommended chain for every sensor.

Tracing a value through the stages

For a direct transformation such as offset, the incoming value is modified. If the filter returns that modified value, it moves downstream.

Under the inferred ordering above, multiply receives the result from offset, rather than the original raw reading. Its result can then proceed to the median stage.

Window-based filters require a broader view than one reading. ESPHome describes the median filter as operating over the last few values. Its configuration includes window_size, send_every, and send_first_at; the documentation also warns that a large median window reacts more slowly to input changes (Sensor Component). No measured response time is provided.

For the sliding-window moving average, the documented example sets both window_size and send_every to 15, while the API identifies the implementation as a sliding-window moving-average filter (Sensor Component; filter API overview). The supplied evidence does not establish undocumented scheduling or timing details beyond those settings.

A throttle filter checks whether the previous incoming value is at least the configured period old. If it is not, the new value is not passed forward (Sensor Component).

Placement therefore matters conceptually: a later stage can receive only a value returned by the stages before it. When a stage produces no value, processing ends for that reading.

The central rule: new_value()

In the ESPHome 2026.9.1 API reference, the base filter interface is:

optional<float> new_value(float value)

new_value() is called whenever a filter receives a new value. Its return type permits two outcomes (Filter class reference):

  1. It returns a floating-point value.
    That value is passed down the chain.

  2. It returns an empty optional.
    Processing stops for that value.

This does not mean the sensor or filter chain is permanently disabled. The interface can be called again when another value arrives.

The lambda-filter API exposes the same contract. A LambdaFilter receives each new chain value and can return a modified value for downstream processing. Returning an empty optional discards that value (LambdaFilter class reference).

The API overview also describes FilterOutValueFilter as forwarding the chain only when it does not receive a configured excluded value (filter API overview).

Raw state and filtered state

ESPHome exposes values from both sides of the chain:

  • get_raw_state() retrieves the sensor value before filters.
  • get_state() retrieves the value that has passed through all filters.

get_raw_state() is NaN if the sensor has not pushed a value. get_state() is NaN if no value has passed through every filter (Sensor Component).

These accessors help distinguish three situations:

  • The sensor has not produced a raw value.
  • A raw value exists but has not completed the chain.
  • A filtered value exists inside ESPHome.

They do not prove that an external system received anything.

The on_value automation runs after filtering. ESPHome documents that it is triggered when a new value has passed through all filters and is published (Sensor Component).

Where publish_state() fits

Calling publish_state() does not bypass sensor filters. ESPHome documents that the supplied value is processed by those filters and, once filtered, propagates through ESPHome and its API to Home Assistant or through MQTT when configured (Sensor Component).

That is a documented propagation path, not a guarantee of external receipt. Network availability, API connectivity, MQTT configuration, broker behavior, and consumer acceptance lie outside the filter contract.

Troubleshooting the chain

When a value is unexpected or missing:

  1. Compare get_raw_state() with get_state().
  2. Follow each transformation from the preceding stage’s output.
  3. Review configured window and sending settings.
  4. Check throttle and filter-out stages for suppression.
  5. Remember that values supplied to publish_state() still encounter filters.
  6. If filtered state exists but no external update appears, investigate API or MQTT propagation separately.

The essential model is simple: a raw value enters the chain, each filter either supplies a downstream value or ends processing for that reading, and internal publication follows only after a value completes the chain.

Sources and further reading

Back to top ↑

Rody

Founder & CEO · RodyTech LLC

Founder of RodyTech LLC in Iowa. I write practical notes on automation, infrastructure, security, and software decisions for builders and business operators.

No comments yet

Leave a comment

Your email address will not be published. Required fields are marked *