mirror of
https://github.com/esphome/esphome.git
synced 2026-01-20 18:09:10 -07:00
Compare commits
1 Commits
esp32_mdns
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c0f43db9e |
@@ -24,14 +24,13 @@ static void register_esp32(MDNSComponent *comp, StaticVector<MDNSService, MDNS_S
|
||||
mdns_instance_name_set(hostname);
|
||||
|
||||
for (const auto &service : services) {
|
||||
// Stack buffer for up to 16 txt records, heap fallback for more
|
||||
SmallBufferWithHeapFallback<16, mdns_txt_item_t> txt_records(service.txt_records.size());
|
||||
auto txt_records = std::make_unique<mdns_txt_item_t[]>(service.txt_records.size());
|
||||
for (size_t i = 0; i < service.txt_records.size(); i++) {
|
||||
const auto &record = service.txt_records[i];
|
||||
// key and value are either compile-time string literals in flash or pointers to dynamic_txt_values_
|
||||
// Both remain valid for the lifetime of this function, and ESP-IDF makes internal copies
|
||||
txt_records.get()[i].key = MDNS_STR_ARG(record.key);
|
||||
txt_records.get()[i].value = MDNS_STR_ARG(record.value);
|
||||
txt_records[i].key = MDNS_STR_ARG(record.key);
|
||||
txt_records[i].value = MDNS_STR_ARG(record.value);
|
||||
}
|
||||
uint16_t port = const_cast<TemplatableValue<uint16_t> &>(service.port).value();
|
||||
err = mdns_service_add(nullptr, MDNS_STR_ARG(service.service_type), MDNS_STR_ARG(service.proto), port,
|
||||
|
||||
@@ -9,6 +9,7 @@ from esphome.const import (
|
||||
CONF_ABOVE,
|
||||
CONF_ACCURACY_DECIMALS,
|
||||
CONF_ALPHA,
|
||||
CONF_BASELINE,
|
||||
CONF_BELOW,
|
||||
CONF_CALIBRATION,
|
||||
CONF_DEVICE_CLASS,
|
||||
@@ -38,7 +39,6 @@ from esphome.const import (
|
||||
CONF_TIMEOUT,
|
||||
CONF_TO,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_TYPE,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
CONF_VALUE,
|
||||
CONF_WEB_SERVER,
|
||||
@@ -107,7 +107,7 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_generator import MockObj, MockObjClass
|
||||
from esphome.util import Registry
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
@@ -574,38 +574,56 @@ async def lambda_filter_to_code(config, filter_id):
|
||||
return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter)
|
||||
|
||||
|
||||
DELTA_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_VALUE): cv.positive_float,
|
||||
cv.Optional(CONF_TYPE, default="absolute"): cv.one_of(
|
||||
"absolute", "percentage", lower=True
|
||||
),
|
||||
}
|
||||
def validate_delta_value(value):
|
||||
if isinstance(value, str) and value.endswith("%"):
|
||||
# Check it's a well-formed percentage, but return the string as-is
|
||||
try:
|
||||
cv.positive_float(value[:-1])
|
||||
return value
|
||||
except cv.Invalid as exc:
|
||||
raise cv.Invalid("Malformed delta % value") from exc
|
||||
return cv.positive_float(value)
|
||||
|
||||
|
||||
# This ideally would be done with `cv.maybe_simple_value` but it doesn't seem to respect the default for min_value.
|
||||
DELTA_SCHEMA = cv.Any(
|
||||
cv.All(
|
||||
{
|
||||
# Ideally this would be 'default=float("inf")' but it doesn't translate well to C++
|
||||
cv.Optional(CONF_MAX_VALUE): validate_delta_value,
|
||||
cv.Optional(CONF_MIN_VALUE, default="0.0"): validate_delta_value,
|
||||
cv.Optional(CONF_BASELINE): cv.templatable(cv.float_),
|
||||
},
|
||||
cv.has_at_least_one_key(CONF_MAX_VALUE, CONF_MIN_VALUE),
|
||||
),
|
||||
validate_delta_value,
|
||||
)
|
||||
|
||||
|
||||
def validate_delta(config):
|
||||
try:
|
||||
value = cv.positive_float(config)
|
||||
return DELTA_SCHEMA({CONF_VALUE: value, CONF_TYPE: "absolute"})
|
||||
except cv.Invalid:
|
||||
pass
|
||||
try:
|
||||
value = cv.percentage(config)
|
||||
return DELTA_SCHEMA({CONF_VALUE: value, CONF_TYPE: "percentage"})
|
||||
except cv.Invalid:
|
||||
pass
|
||||
raise cv.Invalid("Delta filter requires a positive number or percentage value.")
|
||||
def _get_delta(value):
|
||||
if isinstance(value, str):
|
||||
assert value.endswith("%")
|
||||
return 0.0, float(value[:-1])
|
||||
return value, 0.0
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register("delta", DeltaFilter, cv.Any(DELTA_SCHEMA, validate_delta))
|
||||
@FILTER_REGISTRY.register("delta", DeltaFilter, DELTA_SCHEMA)
|
||||
async def delta_filter_to_code(config, filter_id):
|
||||
percentage = config[CONF_TYPE] == "percentage"
|
||||
return cg.new_Pvariable(
|
||||
filter_id,
|
||||
config[CONF_VALUE],
|
||||
percentage,
|
||||
)
|
||||
# The config could be just the min_value, or it could be a dict.
|
||||
max = MockObj("std::numeric_limits<float>::infinity()"), 0
|
||||
if isinstance(config, dict):
|
||||
min = _get_delta(config[CONF_MIN_VALUE])
|
||||
if CONF_MAX_VALUE in config:
|
||||
max = _get_delta(config[CONF_MAX_VALUE])
|
||||
else:
|
||||
min = _get_delta(config)
|
||||
var = cg.new_Pvariable(filter_id, *min, *max)
|
||||
if isinstance(config, dict) and (baseline_lambda := config.get(CONF_BASELINE)):
|
||||
baseline = await cg.process_lambda(
|
||||
baseline_lambda, [(float, "x")], return_type=float
|
||||
)
|
||||
cg.add(var.set_baseline(baseline))
|
||||
return var
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register("or", OrFilter, validate_filters)
|
||||
|
||||
@@ -291,22 +291,27 @@ optional<float> ThrottleWithPriorityFilter::new_value(float value) {
|
||||
}
|
||||
|
||||
// DeltaFilter
|
||||
DeltaFilter::DeltaFilter(float delta, bool percentage_mode)
|
||||
: delta_(delta), current_delta_(delta), last_value_(NAN), percentage_mode_(percentage_mode) {}
|
||||
DeltaFilter::DeltaFilter(float min_a0, float min_a1, float max_a0, float max_a1)
|
||||
: min_a0_(min_a0), min_a1_(min_a1), max_a0_(max_a0), max_a1_(max_a1) {}
|
||||
|
||||
void DeltaFilter::set_baseline(float (*fn)(float)) { this->baseline_ = fn; }
|
||||
|
||||
optional<float> DeltaFilter::new_value(float value) {
|
||||
if (std::isnan(value)) {
|
||||
if (std::isnan(this->last_value_)) {
|
||||
return {};
|
||||
} else {
|
||||
return this->last_value_ = value;
|
||||
}
|
||||
// Always yield the first value.
|
||||
if (std::isnan(this->last_value_)) {
|
||||
this->last_value_ = value;
|
||||
return value;
|
||||
}
|
||||
float diff = fabsf(value - this->last_value_);
|
||||
if (std::isnan(this->last_value_) || (diff > 0.0f && diff >= this->current_delta_)) {
|
||||
if (this->percentage_mode_) {
|
||||
this->current_delta_ = fabsf(value * this->delta_);
|
||||
}
|
||||
return this->last_value_ = value;
|
||||
// calculate min and max using the linear equation
|
||||
float ref = this->baseline_(this->last_value_);
|
||||
float min = fabsf(this->min_a0_ + ref * this->min_a1_);
|
||||
float max = fabsf(this->max_a0_ + ref * this->max_a1_);
|
||||
float delta = fabsf(value - ref);
|
||||
// if there is no reference, e.g. for the first value, just accept this one,
|
||||
// otherwise accept only if within range.
|
||||
if (delta > min && delta <= max) {
|
||||
this->last_value_ = value;
|
||||
return value;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -452,15 +452,21 @@ class HeartbeatFilter : public Filter, public Component {
|
||||
|
||||
class DeltaFilter : public Filter {
|
||||
public:
|
||||
explicit DeltaFilter(float delta, bool percentage_mode);
|
||||
explicit DeltaFilter(float min_a0, float min_a1, float max_a0, float max_a1);
|
||||
|
||||
void set_baseline(float (*fn)(float));
|
||||
|
||||
optional<float> new_value(float value) override;
|
||||
|
||||
protected:
|
||||
float delta_;
|
||||
float current_delta_;
|
||||
// These values represent linear equations for the min and max values but in practice only one of a0 and a1 will be
|
||||
// non-zero Each limit is calculated as fabs(a0 + value * a1)
|
||||
|
||||
float min_a0_, min_a1_, max_a0_, max_a1_;
|
||||
// default baseline is the previous value
|
||||
float (*baseline_)(float) = [](float last_value) { return last_value; };
|
||||
|
||||
float last_value_{NAN};
|
||||
bool percentage_mode_;
|
||||
};
|
||||
|
||||
class OrFilter : public Filter {
|
||||
|
||||
@@ -371,15 +371,13 @@ template<typename T> class FixedVector {
|
||||
/// @brief Helper class for efficient buffer allocation - uses stack for small sizes, heap for large
|
||||
/// This is useful when most operations need a small buffer but occasionally need larger ones.
|
||||
/// The stack buffer avoids heap allocation in the common case, while heap fallback handles edge cases.
|
||||
/// @tparam STACK_SIZE Number of elements in the stack buffer
|
||||
/// @tparam T Element type (default: uint8_t)
|
||||
template<size_t STACK_SIZE, typename T = uint8_t> class SmallBufferWithHeapFallback {
|
||||
template<size_t STACK_SIZE> class SmallBufferWithHeapFallback {
|
||||
public:
|
||||
explicit SmallBufferWithHeapFallback(size_t size) {
|
||||
if (size <= STACK_SIZE) {
|
||||
this->buffer_ = this->stack_buffer_;
|
||||
} else {
|
||||
this->heap_buffer_ = new T[size];
|
||||
this->heap_buffer_ = new uint8_t[size];
|
||||
this->buffer_ = this->heap_buffer_;
|
||||
}
|
||||
}
|
||||
@@ -391,12 +389,12 @@ template<size_t STACK_SIZE, typename T = uint8_t> class SmallBufferWithHeapFallb
|
||||
SmallBufferWithHeapFallback(SmallBufferWithHeapFallback &&) = delete;
|
||||
SmallBufferWithHeapFallback &operator=(SmallBufferWithHeapFallback &&) = delete;
|
||||
|
||||
T *get() { return this->buffer_; }
|
||||
uint8_t *get() { return this->buffer_; }
|
||||
|
||||
private:
|
||||
T stack_buffer_[STACK_SIZE];
|
||||
T *heap_buffer_{nullptr};
|
||||
T *buffer_;
|
||||
uint8_t stack_buffer_[STACK_SIZE];
|
||||
uint8_t *heap_buffer_{nullptr};
|
||||
uint8_t *buffer_;
|
||||
};
|
||||
|
||||
///@}
|
||||
|
||||
@@ -121,6 +121,8 @@ sensor:
|
||||
min_value: -10.0
|
||||
- debounce: 0.1s
|
||||
- delta: 5.0
|
||||
- delta:
|
||||
max_value: 2%
|
||||
- exponential_moving_average:
|
||||
alpha: 0.1
|
||||
send_every: 15
|
||||
|
||||
180
tests/integration/fixtures/sensor_filters_delta.yaml
Normal file
180
tests/integration/fixtures/sensor_filters_delta.yaml
Normal file
@@ -0,0 +1,180 @@
|
||||
esphome:
|
||||
name: test-delta-filters
|
||||
|
||||
host:
|
||||
api:
|
||||
batch_delay: 0ms # Disable batching to receive all state updates
|
||||
logger:
|
||||
level: DEBUG
|
||||
|
||||
sensor:
|
||||
- platform: template
|
||||
name: "Source Sensor 1"
|
||||
id: source_sensor_1
|
||||
accuracy_decimals: 1
|
||||
|
||||
- platform: template
|
||||
name: "Source Sensor 2"
|
||||
id: source_sensor_2
|
||||
accuracy_decimals: 1
|
||||
|
||||
- platform: template
|
||||
name: "Source Sensor 3"
|
||||
id: source_sensor_3
|
||||
accuracy_decimals: 1
|
||||
|
||||
- platform: template
|
||||
name: "Source Sensor 4"
|
||||
id: source_sensor_4
|
||||
accuracy_decimals: 1
|
||||
|
||||
- platform: copy
|
||||
source_id: source_sensor_1
|
||||
name: "Filter Min"
|
||||
id: filter_min
|
||||
filters:
|
||||
- delta:
|
||||
min_value: 10
|
||||
|
||||
- platform: copy
|
||||
source_id: source_sensor_2
|
||||
name: "Filter Max"
|
||||
id: filter_max
|
||||
filters:
|
||||
- delta:
|
||||
max_value: 10
|
||||
|
||||
- platform: copy
|
||||
source_id: source_sensor_3
|
||||
id: test_3_baseline
|
||||
filters:
|
||||
- median:
|
||||
window_size: 6
|
||||
send_every: 1
|
||||
send_first_at: 1
|
||||
|
||||
- platform: copy
|
||||
source_id: source_sensor_3
|
||||
name: "Filter Baseline Max"
|
||||
id: filter_baseline_max
|
||||
filters:
|
||||
- delta:
|
||||
max_value: 10
|
||||
baseline: !lambda return id(test_3_baseline).state;
|
||||
|
||||
- platform: copy
|
||||
source_id: source_sensor_4
|
||||
name: "Filter Zero Delta"
|
||||
id: filter_zero_delta
|
||||
filters:
|
||||
- delta: 0
|
||||
|
||||
script:
|
||||
- id: test_filter_min
|
||||
then:
|
||||
- sensor.template.publish:
|
||||
id: source_sensor_1
|
||||
state: 1.0
|
||||
- delay: 20ms
|
||||
- sensor.template.publish:
|
||||
id: source_sensor_1
|
||||
state: 5.0 # Filtered out
|
||||
- delay: 20ms
|
||||
- sensor.template.publish:
|
||||
id: source_sensor_1
|
||||
state: 12.0
|
||||
- delay: 20ms
|
||||
- sensor.template.publish:
|
||||
id: source_sensor_1
|
||||
state: 8.0 # Filtered out
|
||||
- delay: 20ms
|
||||
- sensor.template.publish:
|
||||
id: source_sensor_1
|
||||
state: -2.0
|
||||
|
||||
- id: test_filter_max
|
||||
then:
|
||||
- sensor.template.publish:
|
||||
id: source_sensor_2
|
||||
state: 1.0
|
||||
- delay: 20ms
|
||||
- sensor.template.publish:
|
||||
id: source_sensor_2
|
||||
state: 5.0
|
||||
- delay: 20ms
|
||||
- sensor.template.publish:
|
||||
id: source_sensor_2
|
||||
state: 40.0 # Filtered out
|
||||
- delay: 20ms
|
||||
- sensor.template.publish:
|
||||
id: source_sensor_2
|
||||
state: 10.0
|
||||
- delay: 20ms
|
||||
- sensor.template.publish:
|
||||
id: source_sensor_2
|
||||
state: -40.0 # Filtered out
|
||||
|
||||
- id: test_filter_baseline_max
|
||||
then:
|
||||
- sensor.template.publish:
|
||||
id: source_sensor_3
|
||||
state: 1.0
|
||||
- delay: 20ms
|
||||
- sensor.template.publish:
|
||||
id: source_sensor_3
|
||||
state: 2.0
|
||||
- delay: 20ms
|
||||
- sensor.template.publish:
|
||||
id: source_sensor_3
|
||||
state: 3.0
|
||||
- delay: 20ms
|
||||
- sensor.template.publish:
|
||||
id: source_sensor_3
|
||||
state: 40.0 # Filtered out
|
||||
- delay: 20ms
|
||||
- sensor.template.publish:
|
||||
id: source_sensor_3
|
||||
state: 20.0 # Filtered out
|
||||
- delay: 20ms
|
||||
- sensor.template.publish:
|
||||
id: source_sensor_3
|
||||
state: 20.0
|
||||
|
||||
- id: test_filter_zero_delta
|
||||
then:
|
||||
- sensor.template.publish:
|
||||
id: source_sensor_4
|
||||
state: 1.0
|
||||
- delay: 20ms
|
||||
- sensor.template.publish:
|
||||
id: source_sensor_4
|
||||
state: 1.0 # Filtered out
|
||||
- delay: 20ms
|
||||
- sensor.template.publish:
|
||||
id: source_sensor_4
|
||||
state: 2.0
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: "Test Filter Min"
|
||||
id: btn_filter_min
|
||||
on_press:
|
||||
- script.execute: test_filter_min
|
||||
|
||||
- platform: template
|
||||
name: "Test Filter Max"
|
||||
id: btn_filter_max
|
||||
on_press:
|
||||
- script.execute: test_filter_max
|
||||
|
||||
- platform: template
|
||||
name: "Test Filter Baseline Max"
|
||||
id: btn_filter_baseline_max
|
||||
on_press:
|
||||
- script.execute: test_filter_baseline_max
|
||||
|
||||
- platform: template
|
||||
name: "Test Filter Zero Delta"
|
||||
id: btn_filter_zero_delta
|
||||
on_press:
|
||||
- script.execute: test_filter_zero_delta
|
||||
163
tests/integration/test_sensor_filters_delta.py
Normal file
163
tests/integration/test_sensor_filters_delta.py
Normal file
@@ -0,0 +1,163 @@
|
||||
"""Test sensor DeltaFilter functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
from aioesphomeapi import ButtonInfo, EntityState, SensorState
|
||||
import pytest
|
||||
|
||||
from .state_utils import InitialStateHelper, build_key_to_entity_mapping
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sensor_filters_delta(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
loop = asyncio.get_running_loop()
|
||||
|
||||
sensor_values: dict[str, list[float]] = {
|
||||
"filter_min": [],
|
||||
"filter_max": [],
|
||||
"filter_baseline_max": [],
|
||||
"filter_zero_delta": [],
|
||||
}
|
||||
|
||||
filter_min_done = loop.create_future()
|
||||
filter_max_done = loop.create_future()
|
||||
filter_baseline_max_done = loop.create_future()
|
||||
filter_zero_delta_done = loop.create_future()
|
||||
|
||||
def on_state(state: EntityState) -> None:
|
||||
if not isinstance(state, SensorState) or state.missing_state:
|
||||
return
|
||||
|
||||
sensor_name = key_to_sensor.get(state.key)
|
||||
if sensor_name not in sensor_values:
|
||||
return
|
||||
|
||||
sensor_values[sensor_name].append(state.state)
|
||||
|
||||
# Check completion conditions
|
||||
if (
|
||||
sensor_name == "filter_min"
|
||||
and len(sensor_values[sensor_name]) == 3
|
||||
and not filter_min_done.done()
|
||||
):
|
||||
filter_min_done.set_result(True)
|
||||
elif (
|
||||
sensor_name == "filter_max"
|
||||
and len(sensor_values[sensor_name]) == 3
|
||||
and not filter_max_done.done()
|
||||
):
|
||||
filter_max_done.set_result(True)
|
||||
elif (
|
||||
sensor_name == "filter_baseline_max"
|
||||
and len(sensor_values[sensor_name]) == 4
|
||||
and not filter_baseline_max_done.done()
|
||||
):
|
||||
filter_baseline_max_done.set_result(True)
|
||||
elif (
|
||||
sensor_name == "filter_zero_delta"
|
||||
and len(sensor_values[sensor_name]) == 2
|
||||
and not filter_zero_delta_done.done()
|
||||
):
|
||||
filter_zero_delta_done.set_result(True)
|
||||
|
||||
async with (
|
||||
run_compiled(yaml_config),
|
||||
api_client_connected() as client,
|
||||
):
|
||||
# Get entities and build key mapping
|
||||
entities, _ = await client.list_entities_services()
|
||||
key_to_sensor = build_key_to_entity_mapping(
|
||||
entities,
|
||||
{
|
||||
"filter_min": "Filter Min",
|
||||
"filter_max": "Filter Max",
|
||||
"filter_baseline_max": "Filter Baseline Max",
|
||||
"filter_zero_delta": "Filter Zero Delta",
|
||||
},
|
||||
)
|
||||
|
||||
# Set up initial state helper with all entities
|
||||
initial_state_helper = InitialStateHelper(entities)
|
||||
|
||||
# Subscribe to state changes with wrapper
|
||||
client.subscribe_states(initial_state_helper.on_state_wrapper(on_state))
|
||||
|
||||
# Wait for initial states
|
||||
await initial_state_helper.wait_for_initial_states()
|
||||
|
||||
# Find all buttons
|
||||
button_name_map = {
|
||||
"Test Filter Min": "filter_min",
|
||||
"Test Filter Max": "filter_max",
|
||||
"Test Filter Baseline Max": "filter_baseline_max",
|
||||
"Test Filter Zero Delta": "filter_zero_delta",
|
||||
}
|
||||
buttons = {}
|
||||
for entity in entities:
|
||||
if isinstance(entity, ButtonInfo) and entity.name in button_name_map:
|
||||
buttons[button_name_map[entity.name]] = entity.key
|
||||
|
||||
assert len(buttons) == 4, f"Expected 3 buttons, found {len(buttons)}"
|
||||
|
||||
# Test 1: Min
|
||||
sensor_values["filter_min"].clear()
|
||||
client.button_command(buttons["filter_min"])
|
||||
try:
|
||||
await asyncio.wait_for(filter_min_done, timeout=2.0)
|
||||
except TimeoutError:
|
||||
pytest.fail(f"Test 1 timed out. Values: {sensor_values['filter_min']}")
|
||||
|
||||
expected = [1.0, 12.0, -2.0]
|
||||
assert sensor_values["filter_min"] == pytest.approx(expected), (
|
||||
f"Test 1 failed: expected {expected}, got {sensor_values['filter_min']}"
|
||||
)
|
||||
|
||||
# Test 2: Max
|
||||
sensor_values["filter_max"].clear()
|
||||
client.button_command(buttons["filter_max"])
|
||||
try:
|
||||
await asyncio.wait_for(filter_max_done, timeout=2.0)
|
||||
except TimeoutError:
|
||||
pytest.fail(f"Test 2 timed out. Values: {sensor_values['filter_max']}")
|
||||
|
||||
expected = [1.0, 5.0, 10.0]
|
||||
assert sensor_values["filter_max"] == pytest.approx(expected), (
|
||||
f"Test 2 failed: expected {expected}, got {sensor_values['filter_max']}"
|
||||
)
|
||||
|
||||
# Test 3: Baseline Max
|
||||
sensor_values["filter_baseline_max"].clear()
|
||||
client.button_command(buttons["filter_baseline_max"])
|
||||
try:
|
||||
await asyncio.wait_for(filter_baseline_max_done, timeout=2.0)
|
||||
except TimeoutError:
|
||||
pytest.fail(
|
||||
f"Test 3 timed out. Values: {sensor_values['filter_baseline_max']}"
|
||||
)
|
||||
|
||||
expected = [1.0, 2.0, 3.0, 20.0]
|
||||
assert sensor_values["filter_baseline_max"] == pytest.approx(expected), (
|
||||
f"Test 3 failed: expected {expected}, got {sensor_values['filter_baseline_max']}"
|
||||
)
|
||||
|
||||
# Test 4: Zero Delta
|
||||
sensor_values["filter_zero_delta"].clear()
|
||||
client.button_command(buttons["filter_zero_delta"])
|
||||
try:
|
||||
await asyncio.wait_for(filter_zero_delta_done, timeout=2.0)
|
||||
except TimeoutError:
|
||||
pytest.fail(
|
||||
f"Test 4 timed out. Values: {sensor_values['filter_zero_delta']}"
|
||||
)
|
||||
|
||||
expected = [1.0, 2.0]
|
||||
assert sensor_values["filter_zero_delta"] == pytest.approx(expected), (
|
||||
f"Test 4 failed: expected {expected}, got {sensor_values['filter_zero_delta']}"
|
||||
)
|
||||
Reference in New Issue
Block a user