# How `motion_off_hits` Affects False Negative Rates in Espectre

> Discover how motion_off_hits in Espectre lowers false negative rates. Learn how to prevent premature state changes and improve accuracy by understanding this key parameter.

- Repository: [Francesco Pace/espectre](https://github.com/francescopace/espectre)
- Tags: deep-dive
- Published: 2026-06-10

---

**Increasing `motion_off_hits` reduces false negative rates by requiring multiple consecutive low-motion evaluations before transitioning from MOTION to IDLE, preventing premature state changes during brief motion dips.**

The `motion_off_hits` parameter is a critical configuration option in the **Espectre** Wi-Fi sensing framework (francescopace/espectre) that controls the persistence of motion detection states. It directly influences the trade-off between missing ongoing motion (false negatives) and incorrectly reporting motion after it has ceased (false positives).

## Understanding the `motion_off_hits` Mechanism

### The CSIManager State Filter

At the core of Espectre's motion detection pipeline is the `CSIManager` class, which implements an edge-driven state filter to stabilize binary sensor outputs. This filter prevents rapid state flickering by requiring a specific number of consecutive evaluations before confirming a state transition.

In [`components/espectre/csi_manager.cpp`](https://github.com/francescopace/espectre/blob/main/components/espectre/csi_manager.cpp), the filter logic determines the required threshold based on the current pending state:

```cpp
uint8_t required_hits = (pending_motion_state_ == MotionState::MOTION) 
    ? motion_on_hits_ 
    : motion_off_hits_;

if (pending_state_hits_ >= required_hits) {
    // State transition confirmed
}

```

When the system is in **MOTION** state and evaluates a low-motion reading, it increments `pending_state_hits_` and checks against `motion_off_hits_`. Only when the consecutive hit count meets or exceeds this threshold does the effective state transition to **IDLE**.

### Parameter Configuration and API

The parameter defaults to **3** and can be configured through multiple interfaces. The public API in [`components/espectre/espectre.h`](https://github.com/francescopace/espectre/blob/main/components/espectre/espectre.h) exposes a setter:

```cpp
void set_motion_off_hits(uint8_t hits) { 
    this->motion_off_hits_ = hits; 
}

```

This value is also honored in the Micro-Espectre Python runtime policy, where [`micro-espectre/src/runtime_policy.py`](https://github.com/francescopace/espectre/blob/main/micro-espectre/src/runtime_policy.py) enforces a minimum value of 1:

```python
self.motion_off_hits = max(1, int(motion_off_hits))

```

## Impact on False Negative Detection

### The Trade-off Between Latency and Reliability

A **false negative** occurs when the binary sensor reports **IDLE** despite genuine motion still occurring. The `motion_off_hits` parameter indirectly governs this by determining how quickly the system may revert to IDLE after motion metrics temporarily dip below the detection threshold.

- **Low values (1-2):** The state can flip to IDLE after just one or two low-motion evaluations. This reduces latency for returning to idle but increases the risk of false negatives during continuous motion that momentarily dips below the threshold due to noise or brief RF fluctuations.

- **High values (5-10):** Requires many consecutive low-motion evaluations before confirming IDLE. This keeps the sensor in MOTION longer, significantly decreasing the probability of missing ongoing motion, but may prolong false positive reports when motion has actually stopped.

### Environment-Specific Tuning Recommendations

The optimal setting depends on your environment's motion dynamics:

- **Short, intermittent motion bursts:** Use lower `motion_off_hits` values (2-3) to avoid lingering MOTION states, accepting a slight increase in false negatives.
- **Continuous motion tracking:** Use higher values (5-8) to maintain MOTION state during brief signal dips, ensuring reliable capture of sustained activity.

## Implementation Details

### Core Logic in csi_manager.cpp

The hit counter logic is implemented in [`components/espectre/csi_manager.cpp`](https://github.com/francescopace/espectre/blob/main/components/espectre/csi_manager.cpp) (lines 111-118). When processing CSI packets, the manager evaluates motion states and maintains the `pending_state_hits_` counter. The system only publishes a state change to the binary sensor when the accumulated hits meet the required threshold defined by `motion_off_hits`.

### Configuration in ESP-Home and Micro-Espectre

In ESP-Home YAML configurations, the parameter is set at the component level:

```yaml
espectre:
  name: living_room_sensor
  motion_on_hits: 3
  motion_off_hits: 5    # Wait for 5 consecutive low-motion evaluations

  evaluation_interval: 25

```

For Micro-Espectre Python deployments, configure via the `RuntimeMotionPolicy`:

```python
from micro_espectre.runtime_policy import RuntimeMotionPolicy

policy = RuntimeMotionPolicy(
    evaluation_interval=25,
    motion_on_hits=3,
    motion_off_hits=4     # Require 4 OFF-hits before IDLE

)

```

## Practical Configuration Examples

### ESP-Home YAML Configuration

Configure the hit filter in your ESP-Home configuration to balance responsiveness and stability:

```yaml
espectre:
  name: my_espectre
  motion_on_hits: 3          # Default activation threshold

  motion_off_hits: 5         # Extended persistence to reduce false negatives

  evaluation_interval: 25

```

The component reads this value in [`espectre.cpp`](https://github.com/francescopace/espectre/blob/main/espectre.cpp) and passes it to `CSIManager` during initialization.

### Micro-Espectre Python Runtime

Adjust the parameter at runtime in Python micro-controller deployments:

```python
from micro_espectre.runtime_policy import RuntimeMotionPolicy

# Create policy with reduced false-negative rate

policy = RuntimeMotionPolicy(
    evaluation_interval=25,
    motion_on_hits=3,
    motion_off_hits=6
)

# Apply to Spectre instance

spectre = Spectre()
spectre.set_runtime_policy(policy)

```

### Direct C++ Integration

For custom components using the native API:

```cpp
#include "espectre.h"

class MotionController : public Component {
  Spectre *spectre_;
 public:
  void setup() override {
    spectre_ = new Spectre();
    spectre_->set_motion_off_hits(4);   // Line 103 in espectre.h
    spectre_->init();
  }
};

```

## Summary

- **`motion_off_hits` controls the exit delay** from MOTION to IDLE state, requiring multiple consecutive low-motion evaluations before transitioning.
- **Higher values reduce false negatives** by maintaining MOTION state through brief signal dips, though they may increase false positive duration.
- **Default value is 3**, tunable via ESP-Home YAML, Python runtime policies, or direct C++ API calls.
- **Implementation spans** [`components/espectre/csi_manager.cpp`](https://github.com/francescopace/espectre/blob/main/components/espectre/csi_manager.cpp) (filter logic), [`components/espectre/espectre.h`](https://github.com/francescopace/espectre/blob/main/components/espectre/espectre.h) (API), and [`micro-espectre/src/runtime_policy.py`](https://github.com/francescopace/espectre/blob/main/micro-espectre/src/runtime_policy.py) (Python support).
- **Unit test validation** exists in [`test/test_csi_manager/test_csi_manager.cpp`](https://github.com/francescopace/espectre/blob/main/test/test_csi_manager/test_csi_manager.cpp) (lines 408-442), confirming the filter behavior.

## Frequently Asked Questions

### What is the default value for `motion_off_hits` in Espectre?

The default value is **3**, as defined in the component initialization and documented in the README. This means the system requires three consecutive low-motion evaluations before transitioning from MOTION to IDLE, providing a balance between responsiveness and stability for most environments.

### Does `motion_off_hits` affect how quickly motion is detected?

No, `motion_off_hits` only governs the transition **from MOTION to IDLE**. The transition **from IDLE to MOTION** is controlled by the separate `motion_on_hits` parameter. These two parameters operate independently, allowing you to tune activation and deactivation persistence separately based on your specific use case requirements.

### How does `motion_off_hits` interact with packet loss or RF noise?

Espectre processes CSI packets at rates up to 100 packets per second. Brief RF fluctuations or occasional missed packets can cause single low-motion evaluations even during genuine motion. The `motion_off_hits` filter smooths out these transient drops, preventing the binary sensor from flickering and ensuring downstream automations (such as Home Assistant triggers) receive stable state signals rather than noisy transitions.

### Can I change `motion_off_hits` at runtime, or only at compile time?

You can configure `motion_off_hits` at runtime through multiple mechanisms. In ESP-Home, set it via YAML configuration. In Micro-Espectre, instantiate a new `RuntimeMotionPolicy` with updated values and apply it to the running Spectre instance. In custom C++ components, call `set_motion_off_hits()` on the Spectre object at any point after initialization but before the main loop begins processing CSI data.