mirror of
https://github.com/esphome/esphome.git
synced 2026-10-13 09:42:50 +00:00
[exponential_moving_average] Add EMA preserved across reboots (#19882)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
co-authored by
J. Nick Koston
parent
6f2d2a2001
commit
d9b3f299e0
+40
@@ -0,0 +1,40 @@
|
||||
esphome:
|
||||
name: test
|
||||
on_boot:
|
||||
then:
|
||||
- sensor.exponential_moving_average.reset: ema_default
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
sensor:
|
||||
- platform: template
|
||||
id: source_sensor
|
||||
unit_of_measurement: "°C"
|
||||
accuracy_decimals: 1
|
||||
device_class: temperature
|
||||
state_class: measurement
|
||||
lambda: return 1.0;
|
||||
- platform: exponential_moving_average
|
||||
id: ema_default
|
||||
name: EMA Default
|
||||
sensor: source_sensor
|
||||
- platform: exponential_moving_average
|
||||
id: ema_alpha
|
||||
name: EMA Alpha
|
||||
sensor: source_sensor
|
||||
alpha: 0.25
|
||||
unit_of_measurement: "K"
|
||||
accuracy_decimals: 3
|
||||
- platform: exponential_moving_average
|
||||
id: ema_time_constant
|
||||
name: EMA Time Constant
|
||||
sensor: source_sensor
|
||||
time_constant: 5min
|
||||
restore: false
|
||||
- platform: exponential_moving_average
|
||||
id: ema_linear
|
||||
name: EMA Linear
|
||||
sensor: source_sensor
|
||||
time_constant: 30s
|
||||
time_weighting: linear
|
||||
@@ -0,0 +1,166 @@
|
||||
"""Tests for the exponential_moving_average sensor."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.components.exponential_moving_average.sensor import CONFIG_SCHEMA
|
||||
|
||||
|
||||
def test_default_alpha_and_restore(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""Without alpha or time_constant, alpha defaults to 0.1 and restore is on."""
|
||||
main_cpp = generate_main(
|
||||
component_config_path("exponential_moving_average_test.yaml")
|
||||
)
|
||||
|
||||
assert (
|
||||
"new(ema_default) exponential_moving_average::ExponentialMovingAverageSensor(source_sensor);"
|
||||
in main_cpp
|
||||
)
|
||||
assert "ema_default->set_alpha(0.1f);" in main_cpp
|
||||
assert "ema_default->set_restore(true);" in main_cpp
|
||||
|
||||
|
||||
def test_alpha(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
main_cpp = generate_main(
|
||||
component_config_path("exponential_moving_average_test.yaml")
|
||||
)
|
||||
|
||||
assert "ema_alpha->set_alpha(0.25f);" in main_cpp
|
||||
assert "ema_alpha->set_time_constant" not in main_cpp
|
||||
|
||||
|
||||
def test_time_constant_replaces_alpha(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
main_cpp = generate_main(
|
||||
component_config_path("exponential_moving_average_test.yaml")
|
||||
)
|
||||
|
||||
assert "ema_time_constant->set_time_constant(300000);" in main_cpp
|
||||
assert "ema_time_constant->set_alpha" not in main_cpp
|
||||
assert "ema_time_constant->set_restore(false);" in main_cpp
|
||||
assert "ema_time_constant->set_time_weighting" not in main_cpp
|
||||
|
||||
|
||||
def test_time_weighting(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
main_cpp = generate_main(
|
||||
component_config_path("exponential_moving_average_test.yaml")
|
||||
)
|
||||
|
||||
assert "ema_linear->set_time_constant(30000);" in main_cpp
|
||||
assert (
|
||||
"ema_linear->set_time_weighting(exponential_moving_average::TIME_WEIGHTING_LINEAR);"
|
||||
in main_cpp
|
||||
)
|
||||
|
||||
|
||||
def test_properties_inherited_from_source(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""Unset properties come from the source sensor, with one extra decimal; set ones are kept."""
|
||||
main_cpp = generate_main(
|
||||
component_config_path("exponential_moving_average_test.yaml")
|
||||
)
|
||||
|
||||
assert "ema_default->set_accuracy_decimals(2);" in main_cpp
|
||||
assert "ema_alpha->set_accuracy_decimals(3);" in main_cpp
|
||||
default_line = next(
|
||||
line for line in main_cpp.splitlines() if '"EMA Default"' in line
|
||||
)
|
||||
alpha_line = next(line for line in main_cpp.splitlines() if '"EMA Alpha"' in line)
|
||||
assert "°C" in default_line
|
||||
assert "temperature" in default_line
|
||||
assert "K" in alpha_line
|
||||
|
||||
|
||||
def test_reset_action(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
main_cpp = generate_main(
|
||||
component_config_path("exponential_moving_average_test.yaml")
|
||||
)
|
||||
|
||||
assert "::ema_default->reset();" in main_cpp
|
||||
|
||||
|
||||
def test_alpha_and_time_constant_are_exclusive() -> None:
|
||||
with pytest.raises(cv.Invalid, match="Cannot specify more than one of"):
|
||||
CONFIG_SCHEMA(
|
||||
{
|
||||
"id": "ema",
|
||||
"name": "EMA",
|
||||
"sensor": "source",
|
||||
"alpha": 0.5,
|
||||
"time_constant": "1min",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("alpha", [0, -0.1, 1.5])
|
||||
def test_alpha_out_of_range(alpha: float) -> None:
|
||||
with pytest.raises(cv.Invalid):
|
||||
CONFIG_SCHEMA({"id": "ema", "name": "EMA", "sensor": "source", "alpha": alpha})
|
||||
|
||||
|
||||
@pytest.mark.parametrize("alpha", [0.01, 1])
|
||||
def test_alpha_in_range(alpha: float) -> None:
|
||||
config = CONFIG_SCHEMA(
|
||||
{"id": "ema", "name": "EMA", "sensor": "source", "alpha": alpha}
|
||||
)
|
||||
|
||||
assert config["alpha"] == alpha
|
||||
|
||||
|
||||
def test_time_weighting_requires_time_constant() -> None:
|
||||
with pytest.raises(cv.Invalid, match="can only be used with 'time_constant'"):
|
||||
CONFIG_SCHEMA(
|
||||
{
|
||||
"id": "ema",
|
||||
"name": "EMA",
|
||||
"sensor": "source",
|
||||
"time_weighting": "previous",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("weighting", ["new", "previous", "linear", "LINEAR"])
|
||||
def test_time_weighting_values(weighting: str) -> None:
|
||||
config = CONFIG_SCHEMA(
|
||||
{
|
||||
"id": "ema",
|
||||
"name": "EMA",
|
||||
"sensor": "source",
|
||||
"time_constant": "1min",
|
||||
"time_weighting": weighting,
|
||||
}
|
||||
)
|
||||
|
||||
assert config["time_weighting"] == weighting.lower()
|
||||
|
||||
|
||||
def test_time_weighting_rejects_unknown_value() -> None:
|
||||
with pytest.raises(cv.Invalid):
|
||||
CONFIG_SCHEMA(
|
||||
{
|
||||
"id": "ema",
|
||||
"name": "EMA",
|
||||
"sensor": "source",
|
||||
"time_constant": "1min",
|
||||
"time_weighting": "trapezoid",
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user