1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-17 23:35:47 +00:00

Compare commits

...

2 Commits

Author SHA1 Message Date
Claude
23624aff09 [motion_map] Fix threading, logging, and remove ESP-IDF restriction
Critical fixes based on review feedback:

Threading fixes:
- CSI callback now only copies data to buffer (runs in WiFi task)
- Actual processing moved to loop() in main task context
- Added CSIDataBuffer structure for safe cross-task communication
- Prevents race conditions and task priority issues

Logging improvements:
- Combined related config messages with newlines (reduces packets)
- Changed verbose logs to ESP_LOGV (state changes)
- Changed info logs to ESP_LOGD (initialization)
- Only warn if CSI fails to initialize
- Removed redundant setup message

Configuration:
- Removed cv.only_with_esp_idf restriction
- C++ guards (#ifdef USE_ESP_IDF) already handle this
- ESP32-S3 variant restriction remains (CSI requirement)

These changes ensure proper task safety and follow ESPHome
logging best practices for network efficiency.
2025-11-17 23:34:32 +00:00
Claude
755357b7c6 [motion_map] Add new component for Wi-Fi CSI-based motion detection
This component implements privacy-preserving motion detection using
Wi-Fi Channel State Information (CSI) analysis, inspired by the
ESPectre project.

Features:
- CSI-based motion detection without cameras or microphones
- Binary sensor for motion state (IDLE/MOTION)
- Multiple feature sensors (variance, amplitude, entropy, skewness)
- Configurable thresholds and sensitivity
- ESP32-S3 with ESP-IDF framework support only

Components:
- Core component with CSI capture and analysis
- Binary sensor platform for motion detection
- Sensor platform for CSI feature extraction
- Comprehensive test configuration
- Full documentation with usage examples

The component uses Moving Variance Segmentation algorithm to detect
motion by analyzing variations in Wi-Fi signal propagation patterns.
Perfect for smart home automation, occupancy detection, and
privacy-conscious security applications.
2025-11-17 23:23:40 +00:00
8 changed files with 806 additions and 0 deletions

View File

@@ -0,0 +1,153 @@
# Motion Map Component
## Overview
The Motion Map component uses Wi-Fi Channel State Information (CSI) to detect motion without cameras or microphones, providing privacy-preserving presence detection for home automation.
## Features
- **Privacy-First**: No cameras or microphones required
- **CSI-Based Detection**: Analyzes Wi-Fi signal variations to detect movement
- **Multiple Sensors**: Provides variance, amplitude, entropy, and skewness measurements
- **Configurable**: Adjustable thresholds and sensitivity
- **ESP32-S3 Optimized**: Takes advantage of ESP32-S3's CSI capabilities
## Platform Support
- **ESP32-S3** with **ESP-IDF** framework only (CSI support required)
## Configuration
### Basic Example
```yaml
wifi:
ssid: "YourSSID"
password: "YourPassword"
motion_map:
id: motion_map_component
motion_threshold: 0.6
idle_threshold: 0.2
window_size: 100
sensitivity: 1.5
binary_sensor:
- platform: motion_map
motion_map_id: motion_map_component
name: "Motion Detected"
sensor:
- platform: motion_map
motion_map_id: motion_map_component
variance:
name: "CSI Variance"
amplitude:
name: "CSI Amplitude"
entropy:
name: "CSI Entropy"
skewness:
name: "CSI Skewness"
```
### Configuration Variables
#### Motion Map Component
- **motion_threshold** (*Optional*, float): Variance threshold for detecting motion. Range: 0.0-1.0. Default: 0.5
- **idle_threshold** (*Optional*, float): Variance threshold for detecting idle state. Range: 0.0-1.0. Default: 0.2
- **window_size** (*Optional*, int): Number of samples for moving window analysis. Range: 10-500. Default: 100
- **sensitivity** (*Optional*, float): Sensitivity multiplier for variance detection. Range: 0.1-5.0. Default: 1.0
- **mac_address** (*Optional*, MAC address): Filter CSI data by specific MAC address
#### Binary Sensor
- **motion_map_id** (*Required*, ID): The ID of the motion_map component
- All standard binary sensor options
#### Sensors
- **motion_map_id** (*Required*, ID): The ID of the motion_map component
- **variance** (*Optional*): CSI variance sensor configuration
- **amplitude** (*Optional*): CSI amplitude sensor configuration
- **entropy** (*Optional*): Signal entropy sensor configuration
- **skewness** (*Optional*): Signal skewness sensor configuration
Each sensor supports all standard sensor configuration options.
## How It Works
1. **CSI Capture**: The component captures Channel State Information from Wi-Fi packets
2. **Signal Analysis**: Calculates variance and amplitude from CSI subcarrier data
3. **Motion Detection**: Uses variance thresholds to determine motion vs. idle state
4. **Feature Extraction**: Computes statistical features (entropy, skewness) from the signal window
5. **State Publishing**: Updates sensors and binary sensors for Home Assistant integration
## Technical Details
### CSI (Channel State Information)
CSI provides detailed radio channel data that reveals how Wi-Fi signals propagate through space. When people move, they alter these propagation patterns, creating detectable electromagnetic changes.
### Detection Algorithm
The component uses a Moving Variance Segmentation algorithm:
- Continuously calculates variance from CSI subcarrier amplitudes
- Compares variance against configurable thresholds
- Transitions between IDLE and MOTION states based on signal characteristics
### Performance Considerations
- **Update Rate**: Sensors update every 1 second
- **Memory Usage**: Window size affects RAM usage (default: 100 samples ≈ 400 bytes)
- **CPU Usage**: CSI processing runs in Wi-Fi callback context
## Use Cases
- **Room Occupancy**: Detect presence in rooms without cameras
- **Smart Lighting**: Trigger lights based on motion
- **Security**: Privacy-preserving motion alerts
- **Multi-Room Mapping**: Deploy multiple sensors for whole-home coverage
- **Activity Recognition**: Use extracted features for ML-based activity classification
## Comparison with PIR Sensors
| Feature | Motion Map (CSI) | PIR Sensor |
|---------|------------------|------------|
| Privacy | High (no imaging) | High |
| Detection Area | 360° coverage | Directional |
| Sensitivity | Configurable | Fixed |
| Through Walls | Limited | No |
| Setup Complexity | Medium | Low |
## Troubleshooting
### CSI Not Initializing
- Verify you're using ESP32-S3 with ESP-IDF framework
- Check that Wi-Fi is enabled and connected
- Review logs for CSI initialization errors
### No Motion Detection
- Adjust `sensitivity` parameter (try 2.0-3.0 for higher sensitivity)
- Lower `motion_threshold` (try 0.3-0.4)
- Increase `window_size` for more stable detection
- Check CSI variance sensor values to verify signal capture
### False Positives
- Increase `motion_threshold` (try 0.7-0.8)
- Increase `idle_threshold` to add hysteresis
- Reduce `sensitivity` parameter
- Use `mac_address` filter to focus on specific transmitter
## Credits
Inspired by the [ESPectre project](https://github.com/francescopace/espectre) by Francesco Pace.
## See Also
- [ESPHome Binary Sensor](https://esphome.io/components/binary_sensor/)
- [ESPHome Sensor](https://esphome.io/components/sensor/)
- [ESP32 CSI Documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-guides/wifi.html#wi-fi-channel-state-information)

View File

@@ -0,0 +1,87 @@
"""Motion Map Component for ESPHome.
This component uses Wi-Fi Channel State Information (CSI) to detect motion
without cameras or microphones, providing privacy-preserving presence detection.
"""
import esphome.codegen as cg
from esphome.components.esp32 import (
VARIANT_ESP32S3,
add_idf_sdkconfig_option,
only_on_variant,
)
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.core import CORE
CODEOWNERS = ["@esphome/core"]
DEPENDENCIES = ["esp32", "wifi"]
AUTO_LOAD = []
motion_map_ns = cg.esphome_ns.namespace("motion_map")
MotionMapComponent = motion_map_ns.class_("MotionMapComponent", cg.Component)
# For sub-components to reference the parent
CONF_MOTION_MAP_ID = "motion_map_id"
# Configuration keys
CONF_MOTION_THRESHOLD = "motion_threshold"
CONF_IDLE_THRESHOLD = "idle_threshold"
CONF_WINDOW_SIZE = "window_size"
CONF_MAC_ADDRESS = "mac_address"
CONF_SENSITIVITY = "sensitivity"
CONFIG_SCHEMA = cv.All(
cv.COMPONENT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(MotionMapComponent),
cv.Optional(CONF_MOTION_THRESHOLD, default=0.5): cv.float_range(
min=0.0, max=1.0
),
cv.Optional(CONF_IDLE_THRESHOLD, default=0.2): cv.float_range(
min=0.0, max=1.0
),
cv.Optional(CONF_WINDOW_SIZE, default=100): cv.int_range(
min=10, max=500
),
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
cv.Optional(CONF_SENSITIVITY, default=1.0): cv.float_range(
min=0.1, max=5.0
),
}
),
only_on_variant(supported=[VARIANT_ESP32S3]),
)
async def to_code(config):
"""Generate C++ code for the motion map component."""
# Enable CSI in ESP-IDF SDK config
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENABLE_CSI", True)
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_CSI_ENABLED", True)
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
cg.add(var.set_motion_threshold(config[CONF_MOTION_THRESHOLD]))
cg.add(var.set_idle_threshold(config[CONF_IDLE_THRESHOLD]))
cg.add(var.set_window_size(config[CONF_WINDOW_SIZE]))
cg.add(var.set_sensitivity(config[CONF_SENSITIVITY]))
if CONF_MAC_ADDRESS in config:
mac_address = config[CONF_MAC_ADDRESS].parts
cg.add(
var.set_mac_address(
[
mac_address[0],
mac_address[1],
mac_address[2],
mac_address[3],
mac_address[4],
mac_address[5],
]
)
)
# Add ESP-IDF component dependencies
if CORE.using_esp_idf:
cg.add_library("esp_wifi", None)

View File

@@ -0,0 +1,35 @@
"""Binary sensor platform for Motion Map component."""
import esphome.codegen as cg
from esphome.components import binary_sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
DEVICE_CLASS_MOTION,
ENTITY_CATEGORY_DIAGNOSTIC,
)
from . import CONF_MOTION_MAP_ID, MotionMapComponent, motion_map_ns
DEPENDENCIES = ["motion_map"]
MotionMapBinarySensor = motion_map_ns.class_(
"MotionMapBinarySensor", binary_sensor.BinarySensor, cg.Component
)
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(
MotionMapBinarySensor,
device_class=DEVICE_CLASS_MOTION,
).extend(
{
cv.GenerateID(CONF_MOTION_MAP_ID): cv.use_id(MotionMapComponent),
}
)
async def to_code(config):
"""Generate code for the motion binary sensor."""
var = await binary_sensor.new_binary_sensor(config)
await cg.register_component(var, config)
parent = await cg.get_variable(config[CONF_MOTION_MAP_ID])
cg.add(parent.set_motion_binary_sensor(var))

View File

@@ -0,0 +1,305 @@
#include "motion_map.h"
#ifdef USE_ESP_IDF
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/log.h"
namespace esphome {
namespace motion_map {
static const char *const TAG = "motion_map";
void MotionMapComponent::setup() {
// Reserve space for variance window
this->variance_window_.reserve(this->window_size_);
// Initialize CSI capture
this->init_csi_();
}
void MotionMapComponent::loop() {
// Process new CSI data if available
if (this->new_csi_data_) {
this->new_csi_data_ = false;
this->process_csi_data_();
}
// Periodic sensor publishing (every 1 second)
uint32_t now = millis();
if (now - this->last_update_time_ >= 1000) {
this->publish_sensors_();
this->last_update_time_ = now;
}
}
void MotionMapComponent::dump_config() {
ESP_LOGCONFIG(TAG, "Motion Map:");
ESP_LOGCONFIG(TAG, " Motion Threshold: %.2f\n Idle Threshold: %.2f\n Window Size: %u\n Sensitivity: %.2f",
this->motion_threshold_, this->idle_threshold_, this->window_size_, this->sensitivity_);
if (this->mac_address_.has_value()) {
ESP_LOGCONFIG(TAG, " MAC Filter: %02X:%02X:%02X:%02X:%02X:%02X", (*this->mac_address_)[0],
(*this->mac_address_)[1], (*this->mac_address_)[2], (*this->mac_address_)[3],
(*this->mac_address_)[4], (*this->mac_address_)[5]);
}
if (!this->csi_initialized_) {
ESP_LOGW(TAG, "CSI not initialized");
}
}
void MotionMapComponent::init_csi_() {
// Configure CSI
wifi_csi_config_t csi_config = {};
csi_config.lltf_en = true; // Enable Long Training Field
csi_config.htltf_en = true; // Enable HT Long Training Field
csi_config.stbc_htltf2_en = false; // Disable STBC HT-LTF2
csi_config.ltf_merge_en = true; // Merge LTF
csi_config.channel_filter_en = true; // Enable channel filter
csi_config.manu_scale = false; // Auto scale
csi_config.shift = 0; // No shift
esp_err_t err = esp_wifi_set_csi_config(&csi_config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set CSI config: %s", esp_err_to_name(err));
return;
}
// Register CSI callback
err = esp_wifi_set_csi_rx_cb(MotionMapComponent::csi_callback_, this);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set CSI RX callback: %s", esp_err_to_name(err));
return;
}
// Enable CSI
err = esp_wifi_set_csi(true);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to enable CSI: %s", esp_err_to_name(err));
return;
}
this->csi_initialized_ = true;
ESP_LOGD(TAG, "CSI initialized");
}
void MotionMapComponent::csi_callback_(void *ctx, wifi_csi_info_t *info) {
auto *component = static_cast<MotionMapComponent *>(ctx);
if (component == nullptr || info == nullptr || info->buf == nullptr || info->len == 0) {
return;
}
// Copy CSI data to buffer for processing in main loop
// This callback runs in WiFi task context
size_t len = std::min(static_cast<size_t>(info->len), MAX_CSI_LEN);
memcpy(component->csi_buffer_.data.data(), info->buf, len);
component->csi_buffer_.len = len;
memcpy(component->csi_buffer_.mac.data(), info->mac, 6);
component->csi_buffer_.valid = true;
component->new_csi_data_ = true;
}
void MotionMapComponent::process_csi_data_() {
if (!this->csi_buffer_.valid) {
return;
}
// Filter by MAC address if configured
if (this->mac_address_.has_value()) {
bool mac_match = true;
for (size_t i = 0; i < 6; i++) {
if (this->csi_buffer_.mac[i] != (*this->mac_address_)[i]) {
mac_match = false;
break;
}
}
if (!mac_match) {
return;
}
}
// Extract CSI data from buffer
const int8_t *csi_data = this->csi_buffer_.data.data();
size_t csi_len = this->csi_buffer_.len;
// Calculate variance and amplitude
float variance = this->calculate_variance_(csi_data, csi_len);
float amplitude = this->calculate_amplitude_(csi_data, csi_len);
// Apply sensitivity scaling
variance *= this->sensitivity_;
// Update moving window
this->variance_window_.push_back(variance);
if (this->variance_window_.size() > this->window_size_) {
this->variance_window_.erase(this->variance_window_.begin());
}
// Store current values
this->current_variance_ = variance;
this->current_amplitude_ = amplitude;
// Update motion state
this->update_motion_state_(variance);
}
float MotionMapComponent::calculate_variance_(const int8_t *data, size_t len) {
if (len == 0)
return 0.0f;
// Calculate mean
float sum = 0.0f;
for (size_t i = 0; i < len; i++) {
sum += static_cast<float>(data[i]);
}
float mean = sum / static_cast<float>(len);
// Calculate variance
float variance = 0.0f;
for (size_t i = 0; i < len; i++) {
float diff = static_cast<float>(data[i]) - mean;
variance += diff * diff;
}
variance /= static_cast<float>(len);
return variance;
}
float MotionMapComponent::calculate_amplitude_(const int8_t *data, size_t len) {
if (len == 0)
return 0.0f;
// Calculate RMS amplitude
float sum_sq = 0.0f;
for (size_t i = 0; i < len; i++) {
float val = static_cast<float>(data[i]);
sum_sq += val * val;
}
return sqrtf(sum_sq / static_cast<float>(len));
}
float MotionMapComponent::calculate_entropy_() {
if (this->variance_window_.empty())
return 0.0f;
// Simple entropy calculation using histogram
const int num_bins = 10;
std::array<int, num_bins> histogram = {};
// Find min/max for binning
float min_val = this->variance_window_[0];
float max_val = this->variance_window_[0];
for (float val : this->variance_window_) {
min_val = std::min(min_val, val);
max_val = std::max(max_val, val);
}
float range = max_val - min_val;
if (range < 0.0001f)
return 0.0f;
// Build histogram
for (float val : this->variance_window_) {
int bin = static_cast<int>(((val - min_val) / range) * (num_bins - 1));
bin = std::max(0, std::min(num_bins - 1, bin));
histogram[bin]++;
}
// Calculate entropy
float entropy = 0.0f;
float total = static_cast<float>(this->variance_window_.size());
for (int count : histogram) {
if (count > 0) {
float p = static_cast<float>(count) / total;
entropy -= p * logf(p);
}
}
return entropy;
}
float MotionMapComponent::calculate_skewness_() {
if (this->variance_window_.size() < 3)
return 0.0f;
// Calculate mean
float sum = 0.0f;
for (float val : this->variance_window_) {
sum += val;
}
float mean = sum / static_cast<float>(this->variance_window_.size());
// Calculate standard deviation and skewness
float variance = 0.0f;
float skewness_sum = 0.0f;
for (float val : this->variance_window_) {
float diff = val - mean;
variance += diff * diff;
skewness_sum += diff * diff * diff;
}
float n = static_cast<float>(this->variance_window_.size());
variance /= n;
float std_dev = sqrtf(variance);
if (std_dev < 0.0001f)
return 0.0f;
float skewness = (skewness_sum / n) / (std_dev * std_dev * std_dev);
return skewness;
}
void MotionMapComponent::update_motion_state_(float variance) {
MotionState new_state = this->current_state_;
// Simple threshold-based state machine
if (this->current_state_ == MotionState::IDLE) {
if (variance > this->motion_threshold_) {
new_state = MotionState::MOTION;
}
} else { // MOTION
if (variance < this->idle_threshold_) {
new_state = MotionState::IDLE;
}
}
// Update state if changed
if (new_state != this->current_state_) {
this->current_state_ = new_state;
ESP_LOGV(TAG, "State: %s", new_state == MotionState::MOTION ? "MOTION" : "IDLE");
// Publish binary sensor immediately on state change
if (this->motion_binary_sensor_ != nullptr) {
this->motion_binary_sensor_->publish_state(new_state == MotionState::MOTION);
}
}
}
void MotionMapComponent::publish_sensors_() {
// Publish variance sensor
if (this->variance_sensor_ != nullptr) {
this->variance_sensor_->publish_state(this->current_variance_);
}
// Publish amplitude sensor
if (this->amplitude_sensor_ != nullptr) {
this->amplitude_sensor_->publish_state(this->current_amplitude_);
}
// Publish entropy sensor
if (this->entropy_sensor_ != nullptr && !this->variance_window_.empty()) {
float entropy = this->calculate_entropy_();
this->entropy_sensor_->publish_state(entropy);
}
// Publish skewness sensor
if (this->skewness_sensor_ != nullptr && this->variance_window_.size() >= 3) {
float skewness = this->calculate_skewness_();
this->skewness_sensor_->publish_state(skewness);
}
}
} // namespace motion_map
} // namespace esphome
#endif // USE_ESP_IDF

View File

@@ -0,0 +1,128 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#ifdef USE_ESP_IDF
#include "esp_wifi.h"
#include "esp_wifi_types.h"
#include <array>
#include <cmath>
#include <vector>
namespace esphome {
// Forward declarations
namespace binary_sensor {
class BinarySensor;
}
namespace sensor {
class Sensor;
}
namespace motion_map {
/// Motion state enumeration
enum class MotionState : uint8_t {
IDLE = 0,
MOTION = 1,
};
/// Maximum CSI buffer size for ESP32-S3
static constexpr size_t MAX_CSI_LEN = 384;
/// CSI data buffer for cross-task communication
struct CSIDataBuffer {
std::array<int8_t, MAX_CSI_LEN> data;
size_t len{0};
std::array<uint8_t, 6> mac;
bool valid{false};
};
/**
* @brief Motion Map Component using Wi-Fi CSI for motion detection
*
* This component captures Channel State Information (CSI) from Wi-Fi packets
* and analyzes signal variations to detect motion without cameras or microphones.
*/
class MotionMapComponent : public Component {
public:
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
// Configuration setters
void set_motion_threshold(float threshold) { this->motion_threshold_ = threshold; }
void set_idle_threshold(float threshold) { this->idle_threshold_ = threshold; }
void set_window_size(uint32_t size) { this->window_size_ = size; }
void set_sensitivity(float sensitivity) { this->sensitivity_ = sensitivity; }
void set_mac_address(const std::array<uint8_t, 6> &mac) { this->mac_address_ = mac; }
// Sensor setters
void set_motion_binary_sensor(binary_sensor::BinarySensor *sensor) { this->motion_binary_sensor_ = sensor; }
void set_variance_sensor(sensor::Sensor *sensor) { this->variance_sensor_ = sensor; }
void set_amplitude_sensor(sensor::Sensor *sensor) { this->amplitude_sensor_ = sensor; }
void set_entropy_sensor(sensor::Sensor *sensor) { this->entropy_sensor_ = sensor; }
void set_skewness_sensor(sensor::Sensor *sensor) { this->skewness_sensor_ = sensor; }
protected:
/// Initialize CSI capture
void init_csi_();
/// CSI callback (static wrapper for ESP-IDF) - runs in WiFi task
static void csi_callback_(void *ctx, wifi_csi_info_t *info);
/// Process CSI data in main loop
void process_csi_data_();
/// Calculate variance from CSI data
float calculate_variance_(const int8_t *data, size_t len);
/// Calculate amplitude from CSI data
float calculate_amplitude_(const int8_t *data, size_t len);
/// Calculate entropy from variance window
float calculate_entropy_();
/// Calculate skewness from variance window
float calculate_skewness_();
/// Update motion state based on current variance
void update_motion_state_(float variance);
/// Publish sensor values
void publish_sensors_();
// Configuration parameters
float motion_threshold_{0.5f};
float idle_threshold_{0.2f};
uint32_t window_size_{100};
float sensitivity_{1.0f};
optional<std::array<uint8_t, 6>> mac_address_;
// Sensors
binary_sensor::BinarySensor *motion_binary_sensor_{nullptr};
sensor::Sensor *variance_sensor_{nullptr};
sensor::Sensor *amplitude_sensor_{nullptr};
sensor::Sensor *entropy_sensor_{nullptr};
sensor::Sensor *skewness_sensor_{nullptr};
// Runtime state
MotionState current_state_{MotionState::IDLE};
std::vector<float> variance_window_;
float current_variance_{0.0f};
float current_amplitude_{0.0f};
uint32_t last_update_time_{0};
bool csi_initialized_{false};
// CSI data buffer (written by WiFi task, read by main loop)
CSIDataBuffer csi_buffer_;
volatile bool new_csi_data_{false};
};
} // namespace motion_map
} // namespace esphome
#endif // USE_ESP_IDF

View File

@@ -0,0 +1,70 @@
"""Sensor platform for Motion Map component - CSI feature sensors."""
import esphome.codegen as cg
from esphome.components import sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
)
from . import CONF_MOTION_MAP_ID, MotionMapComponent, motion_map_ns
DEPENDENCIES = ["motion_map"]
# Sensor types for CSI features
CONF_VARIANCE = "variance"
CONF_AMPLITUDE = "amplitude"
CONF_ENTROPY = "entropy"
CONF_SKEWNESS = "skewness"
MotionMapSensor = motion_map_ns.class_(
"MotionMapSensor", sensor.Sensor, cg.Component
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_MOTION_MAP_ID): cv.use_id(MotionMapComponent),
cv.Optional(CONF_VARIANCE): sensor.sensor_schema(
accuracy_decimals=3,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_AMPLITUDE): sensor.sensor_schema(
accuracy_decimals=2,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_ENTROPY): sensor.sensor_schema(
accuracy_decimals=3,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_SKEWNESS): sensor.sensor_schema(
accuracy_decimals=3,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
async def to_code(config):
"""Generate code for the motion map sensors."""
parent = await cg.get_variable(config[CONF_MOTION_MAP_ID])
if variance_config := config.get(CONF_VARIANCE):
sens = await sensor.new_sensor(variance_config)
cg.add(parent.set_variance_sensor(sens))
if amplitude_config := config.get(CONF_AMPLITUDE):
sens = await sensor.new_sensor(amplitude_config)
cg.add(parent.set_amplitude_sensor(sens))
if entropy_config := config.get(CONF_ENTROPY):
sens = await sensor.new_sensor(entropy_config)
cg.add(parent.set_entropy_sensor(sens))
if skewness_config := config.get(CONF_SKEWNESS):
sens = await sensor.new_sensor(skewness_config)
cg.add(parent.set_skewness_sensor(sens))

View File

@@ -0,0 +1,27 @@
wifi:
ssid: MySSID
password: password1
motion_map:
id: motion_map_component
motion_threshold: 0.6
idle_threshold: 0.2
window_size: 100
sensitivity: 1.5
binary_sensor:
- platform: motion_map
motion_map_id: motion_map_component
name: "Motion Detected"
sensor:
- platform: motion_map
motion_map_id: motion_map_component
variance:
name: "CSI Variance"
amplitude:
name: "CSI Amplitude"
entropy:
name: "CSI Entropy"
skewness:
name: "CSI Skewness"

View File

@@ -0,0 +1 @@
<<: !include common.yaml