mirror of
https://github.com/esphome/esphome.git
synced 2026-11-15 01:25:57 +00:00
Compare commits
76
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
222d330464 | ||
|
|
0fffaa5c4f | ||
|
|
55a6c1183c | ||
|
|
a0ab3d2634 | ||
|
|
f8598751bc | ||
|
|
89961864cf | ||
|
|
7a51ca87b4 | ||
|
|
87e7acdc2b | ||
|
|
247e31e4d2 | ||
|
|
6521dd8269 | ||
|
|
bdf4643b97 | ||
|
|
bb9ec63e6b | ||
|
|
aeb77b1e6b | ||
|
|
e627f13b42 | ||
|
|
c4ebe611dd | ||
|
|
3061f9a9c1 | ||
|
|
c4133a1af5 | ||
|
|
c2f651b803 | ||
|
|
c7472e9068 | ||
|
|
2fdd4434dd | ||
|
|
acc7fd2143 | ||
|
|
fc9743e5c7 | ||
|
|
08880dda0a | ||
|
|
a26ab932e2 | ||
|
|
3fd62a666c | ||
|
|
79f1c56ca8 | ||
|
|
3ebb022f55 | ||
|
|
cf5c08c07b | ||
|
|
e732191329 | ||
|
|
2be6a24507 | ||
|
|
2b355fcb14 | ||
|
|
5a838cf0e6 | ||
|
|
ff8a3ea2be | ||
|
|
3a8e90615a | ||
|
|
d3963a82db | ||
|
|
206ab92115 | ||
|
|
426efc1666 | ||
|
|
2c7748606e | ||
|
|
907b3c6d6a | ||
|
|
967c61da4c | ||
|
|
7f7e4af3ea | ||
|
|
a230ffd928 | ||
|
|
62d074aae6 | ||
|
|
250ed963cc | ||
|
|
007eadedb3 | ||
|
|
50f206f9ff | ||
|
|
ed076e9210 | ||
|
|
9aa0a835c4 | ||
|
|
8c9d117be5 | ||
|
|
1c33d6b960 | ||
|
|
79ccc2b5c1 | ||
|
|
be88427d00 | ||
|
|
fbeb90208b | ||
|
|
7d5757e78e | ||
|
|
b593034a2f | ||
|
|
576c635138 | ||
|
|
1dd7f6045d | ||
|
|
a644d8d9a0 | ||
|
|
f2651d794d | ||
|
|
2ed6453467 | ||
|
|
9f6b78930b | ||
|
|
1bca56ba04 | ||
|
|
cc7a9db510 | ||
|
|
a20755a857 | ||
|
|
9389b5b078 | ||
|
|
5859db98cb | ||
|
|
3161162207 | ||
|
|
12ee725395 | ||
|
|
9a8fbf2e2b | ||
|
|
c0ff8d7e98 | ||
|
|
15aaaae719 | ||
|
|
88eaaa86c8 | ||
|
|
a493403ef5 | ||
|
|
aac2f28df2 | ||
|
|
5daa9693d0 | ||
|
|
1e6a3c55b6 |
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import logging
|
||||
import math
|
||||
import struct
|
||||
from typing import Any
|
||||
|
||||
from esphome import automation
|
||||
@@ -301,8 +302,6 @@ ApplyFilter = sensor_ns.class_("ApplyFilter", Filter)
|
||||
APPLY_FILTER = automation.ApplyFilterDomain(
|
||||
FILTER_REGISTRY, ApplyFilter, cg.optional.template(cg.float_), ((cg.float_, "x"),)
|
||||
)
|
||||
OffsetFilter = sensor_ns.class_("OffsetFilter", Filter)
|
||||
MultiplyFilter = sensor_ns.class_("MultiplyFilter", Filter)
|
||||
ValueListFilter = sensor_ns.class_("ValueListFilter", Filter)
|
||||
FilterOutValueFilter = sensor_ns.class_("FilterOutValueFilter", ValueListFilter)
|
||||
ThrottleFilter = sensor_ns.class_("ThrottleFilter", Filter)
|
||||
@@ -321,11 +320,8 @@ HeartbeatFilter = sensor_ns.class_("HeartbeatFilter", Filter)
|
||||
DeltaFilter = sensor_ns.class_("DeltaFilter", Filter)
|
||||
OrFilter = sensor_ns.class_("OrFilter", Filter)
|
||||
CalibrateLinearFilter = sensor_ns.class_("CalibrateLinearFilter", Filter)
|
||||
ToNTCResistanceFilter = sensor_ns.class_("ToNTCResistanceFilter", Filter)
|
||||
ToNTCTemperatureFilter = sensor_ns.class_("ToNTCTemperatureFilter", Filter)
|
||||
RHCorrectionFilter = sensor_ns.class_("RHCorrectionFilter", Filter)
|
||||
CalibratePolynomialFilter = sensor_ns.class_("CalibratePolynomialFilter", Filter)
|
||||
ClampFilter = sensor_ns.class_("ClampFilter", Filter)
|
||||
RoundFilter = sensor_ns.class_("RoundFilter", Filter)
|
||||
RoundMultipleFilter = sensor_ns.class_("RoundMultipleFilter", Filter)
|
||||
RoundSignificantDigitsFilter = sensor_ns.class_("RoundSignificantDigitsFilter", Filter)
|
||||
@@ -424,16 +420,16 @@ def sensor_schema(
|
||||
return _SENSOR_SCHEMA.extend(schema)
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register("offset", OffsetFilter, cv.templatable(cv.float_))
|
||||
async def offset_filter_to_code(config, filter_id):
|
||||
template_ = await cg.templatable(config, [], cg.float_)
|
||||
return cg.new_Pvariable(filter_id, template_)
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register("multiply", MultiplyFilter, cv.templatable(cv.float_))
|
||||
async def multiply_filter_to_code(config, filter_id):
|
||||
template_ = await cg.templatable(config, [], cg.float_)
|
||||
return cg.new_Pvariable(filter_id, template_)
|
||||
APPLY_FILTER.register(
|
||||
"offset",
|
||||
cv.templatable(cv.float_),
|
||||
automation.ApplyCall("return x + {};", ((automation.WHOLE_CONFIG, cg.float_),)),
|
||||
)
|
||||
APPLY_FILTER.register(
|
||||
"multiply",
|
||||
cv.templatable(cv.float_),
|
||||
automation.ApplyCall("return x * {};", ((automation.WHOLE_CONFIG, cg.float_),)),
|
||||
)
|
||||
|
||||
|
||||
TemplatableFloat = cg.esphome_ns.class_("TemplatableFn").template(cg.float_)
|
||||
@@ -838,6 +834,18 @@ def validate_calibrate_linear(config):
|
||||
LINEAR_SEGMENT = cg.std_ns.class_("array").template(cg.float_, 3)
|
||||
|
||||
|
||||
_LINEAR = automation.ApplyCall(
|
||||
"return x * {} + {};", (("k", cg.float_), ("b", cg.float_))
|
||||
)
|
||||
_LINEAR_SCHEMA = cv.Schema({cv.Required("k"): cv.float_, cv.Required("b"): cv.float_})
|
||||
|
||||
|
||||
async def _linear_filter(filter_id: ID, k: float, b: float) -> MockObj:
|
||||
return await APPLY_FILTER.build(
|
||||
"calibrate_linear", {"k": k, "b": b}, filter_id, _LINEAR, schema=_LINEAR_SCHEMA
|
||||
)
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register(
|
||||
"calibrate_linear",
|
||||
CalibrateLinearFilter,
|
||||
@@ -858,12 +866,12 @@ async def calibrate_linear_filter_to_code(config, filter_id):
|
||||
x = [conf[CONF_FROM] for conf in config[CONF_DATAPOINTS]]
|
||||
y = [conf[CONF_TO] for conf in config[CONF_DATAPOINTS]]
|
||||
|
||||
linear_functions = []
|
||||
if config[CONF_METHOD] == "least_squares":
|
||||
k, b = fit_linear(x, y)
|
||||
linear_functions = [[k, b, float("NaN")]]
|
||||
elif config[CONF_METHOD] == "exact":
|
||||
linear_functions = map_linear(x, y)
|
||||
return await _linear_filter(filter_id, *fit_linear(x, y))
|
||||
linear_functions = map_linear(x, y)
|
||||
if len(linear_functions) == 1:
|
||||
# One segment needs no table
|
||||
return await _linear_filter(filter_id, *linear_functions[0][:2])
|
||||
# The last segment always has a NaN boundary, so the filter needs no count.
|
||||
table = cg.shared_progmem_array(
|
||||
"sensor_calibrate_linear", LINEAR_SEGMENT, linear_functions
|
||||
@@ -935,14 +943,31 @@ CLAMP_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register("clamp", ClampFilter, CLAMP_SCHEMA)
|
||||
async def clamp_filter_to_code(config, filter_id):
|
||||
return cg.new_Pvariable(
|
||||
filter_id,
|
||||
config[CONF_MIN_VALUE],
|
||||
config[CONF_MAX_VALUE],
|
||||
config[CONF_IGNORE_OUT_OF_RANGE],
|
||||
)
|
||||
def _clamp_bound(config: ConfigType, value: float) -> str:
|
||||
"""The bound as written, NAN for no bound.
|
||||
|
||||
A literal beyond float range was infinite in the old ClampFilter, so no bound; one that
|
||||
rounds to FLT_MAX stays a bound. The compiler rounds the literal, as it always did.
|
||||
"""
|
||||
try:
|
||||
bound = struct.unpack("f", struct.pack("f", value))[0]
|
||||
except OverflowError:
|
||||
return "NAN"
|
||||
return str(cg.safe_exp(value)) if math.isfinite(bound) else "NAN"
|
||||
|
||||
|
||||
APPLY_FILTER.register(
|
||||
"clamp",
|
||||
CLAMP_SCHEMA,
|
||||
automation.ApplyCall(
|
||||
"return sensor::clamp_filter_value(x, {}, {}, {});",
|
||||
(
|
||||
(CONF_MIN_VALUE, cg.float_, _clamp_bound),
|
||||
(CONF_MAX_VALUE, cg.float_, _clamp_bound),
|
||||
(CONF_IGNORE_OUT_OF_RANGE, cg.bool_),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register(
|
||||
@@ -1235,46 +1260,26 @@ def ntc_process_calibration(value):
|
||||
}
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register(
|
||||
_NTC_SCHEMA = cv.Schema({cv.Required(CONF_CALIBRATION): ntc_process_calibration})
|
||||
_NTC_COEFFICIENTS = tuple(
|
||||
((CONF_CALIBRATION, key), cg.double) for key in (CONF_A, CONF_B, CONF_C)
|
||||
)
|
||||
APPLY_FILTER.register(
|
||||
"to_ntc_resistance",
|
||||
ToNTCResistanceFilter,
|
||||
cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_CALIBRATION): ntc_process_calibration,
|
||||
}
|
||||
),
|
||||
_NTC_SCHEMA,
|
||||
automation.ApplyCall(
|
||||
"return sensor::steinhart_hart_resistance(x, {}, {}, {});",
|
||||
_NTC_COEFFICIENTS,
|
||||
),
|
||||
)
|
||||
async def calibrate_ntc_resistance_filter_to_code(config, filter_id):
|
||||
calib = config[CONF_CALIBRATION]
|
||||
return cg.new_Pvariable(
|
||||
filter_id,
|
||||
calib[CONF_A],
|
||||
calib[CONF_B],
|
||||
calib[CONF_C],
|
||||
)
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register(
|
||||
APPLY_FILTER.register(
|
||||
"to_ntc_temperature",
|
||||
ToNTCTemperatureFilter,
|
||||
cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_CALIBRATION): ntc_process_calibration,
|
||||
}
|
||||
),
|
||||
_NTC_SCHEMA,
|
||||
automation.ApplyCall(
|
||||
"return sensor::steinhart_hart_temperature(x, {}, {}, {});",
|
||||
_NTC_COEFFICIENTS,
|
||||
),
|
||||
)
|
||||
async def calibrate_ntc_temperature_filter_to_code(config, filter_id):
|
||||
calib = config[CONF_CALIBRATION]
|
||||
return cg.new_Pvariable(
|
||||
filter_id,
|
||||
calib[CONF_A],
|
||||
calib[CONF_B],
|
||||
calib[CONF_C],
|
||||
)
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register(
|
||||
|
||||
@@ -215,16 +215,6 @@ optional<float> LambdaFilter::new_value(float value) {
|
||||
return it;
|
||||
}
|
||||
|
||||
// OffsetFilter
|
||||
OffsetFilter::OffsetFilter(TemplatableFn<float> offset) : offset_(offset) {}
|
||||
|
||||
optional<float> OffsetFilter::new_value(float value) { return value + this->offset_.value(); }
|
||||
|
||||
// MultiplyFilter
|
||||
MultiplyFilter::MultiplyFilter(TemplatableFn<float> multiplier) : multiplier_(multiplier) {}
|
||||
|
||||
optional<float> MultiplyFilter::new_value(float value) { return value * this->multiplier_.value(); }
|
||||
|
||||
// ValueListFilter
|
||||
bool ValueListFilter::value_matches_any_(float sensor_value) {
|
||||
int8_t accuracy = this->parent_->get_accuracy_decimals();
|
||||
@@ -433,25 +423,6 @@ optional<float> CalibratePolynomialFilter::new_value(float value) {
|
||||
return res;
|
||||
}
|
||||
|
||||
ClampFilter::ClampFilter(float min, float max, bool ignore_out_of_range)
|
||||
: min_(min), max_(max), ignore_out_of_range_(ignore_out_of_range) {}
|
||||
optional<float> ClampFilter::new_value(float value) {
|
||||
if (std::isfinite(this->min_) && !(value >= this->min_)) {
|
||||
if (this->ignore_out_of_range_) {
|
||||
return {};
|
||||
}
|
||||
return this->min_;
|
||||
}
|
||||
|
||||
if (std::isfinite(this->max_) && !(value <= this->max_)) {
|
||||
if (this->ignore_out_of_range_) {
|
||||
return {};
|
||||
}
|
||||
return this->max_;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
RoundFilter::RoundFilter(uint8_t precision) : precision_(precision) {}
|
||||
optional<float> RoundFilter::new_value(float value) {
|
||||
if (std::isfinite(value)) {
|
||||
@@ -469,25 +440,33 @@ optional<float> RoundMultipleFilter::new_value(float value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
optional<float> ToNTCResistanceFilter::new_value(float value) {
|
||||
if (!std::isfinite(value)) {
|
||||
optional<float> clamp_filter_value(float value, float min, float max, bool ignore_out_of_range) {
|
||||
if (std::isfinite(min) && !(value >= min))
|
||||
return ignore_out_of_range ? optional<float>{} : min;
|
||||
if (std::isfinite(max) && !(value <= max))
|
||||
return ignore_out_of_range ? optional<float>{} : max;
|
||||
return value;
|
||||
}
|
||||
|
||||
float steinhart_hart_resistance(float temperature, double a, double b, double c) {
|
||||
if (!std::isfinite(temperature)) {
|
||||
return NAN;
|
||||
}
|
||||
double k = 273.15;
|
||||
// https://de.wikipedia.org/wiki/Steinhart-Hart-Gleichung#cite_note-stein2_s4-3
|
||||
double t = static_cast<double>(value) + k;
|
||||
double y = (this->a_ - 1 / (t)) / (2 * this->c_);
|
||||
double x = sqrt(pow(this->b_ / (3 * this->c_), 3) + y * y);
|
||||
double t = static_cast<double>(temperature) + k;
|
||||
double y = (a - 1 / (t)) / (2 * c);
|
||||
double x = sqrt(pow(b / (3 * c), 3) + y * y);
|
||||
double resistance = exp(pow(x - y, 1 / 3.0) - pow(x + y, 1 / 3.0));
|
||||
return resistance;
|
||||
}
|
||||
|
||||
optional<float> ToNTCTemperatureFilter::new_value(float value) {
|
||||
if (!std::isfinite(value)) {
|
||||
float steinhart_hart_temperature(float resistance, double a, double b, double c) {
|
||||
if (!std::isfinite(resistance)) {
|
||||
return NAN;
|
||||
}
|
||||
double lr = log(double(value));
|
||||
double v = this->a_ + this->b_ * lr + this->c_ * lr * lr * lr;
|
||||
double lr = log(double(resistance));
|
||||
double v = a + b * lr + c * lr * lr * lr;
|
||||
double temp = float(1.0 / v - 273.15);
|
||||
return temp;
|
||||
}
|
||||
|
||||
@@ -300,27 +300,6 @@ template<auto Fn> class ApplyFilter final : public Filter {
|
||||
optional<float> new_value(float value) override { return Fn(value); }
|
||||
};
|
||||
|
||||
/// A simple filter that adds `offset` to each value it receives.
|
||||
class OffsetFilter : public Filter {
|
||||
public:
|
||||
explicit OffsetFilter(TemplatableFn<float> offset);
|
||||
|
||||
optional<float> new_value(float value) override;
|
||||
|
||||
protected:
|
||||
TemplatableFn<float> offset_;
|
||||
};
|
||||
|
||||
/// A simple filter that multiplies to each value it receives by `multiplier`.
|
||||
class MultiplyFilter : public Filter {
|
||||
public:
|
||||
explicit MultiplyFilter(TemplatableFn<float> multiplier);
|
||||
optional<float> new_value(float value) override;
|
||||
|
||||
protected:
|
||||
TemplatableFn<float> multiplier_;
|
||||
};
|
||||
|
||||
// Value tables are read in place from flash, which ESP8266 only allows for whole words.
|
||||
static_assert(sizeof(TemplatableFn<float>) == sizeof(void *), "TemplatableFn<float> must stay a single pointer");
|
||||
|
||||
@@ -574,17 +553,6 @@ class CalibratePolynomialFilter : public Filter {
|
||||
const float *coefficients_; // [count, c0, c1, ...]
|
||||
};
|
||||
|
||||
class ClampFilter : public Filter {
|
||||
public:
|
||||
ClampFilter(float min, float max, bool ignore_out_of_range);
|
||||
optional<float> new_value(float value) override;
|
||||
|
||||
protected:
|
||||
float min_{NAN};
|
||||
float max_{NAN};
|
||||
bool ignore_out_of_range_;
|
||||
};
|
||||
|
||||
class RoundFilter : public Filter {
|
||||
public:
|
||||
explicit RoundFilter(uint8_t precision);
|
||||
@@ -616,27 +584,13 @@ template<uint8_t Digits> class RoundSignificantDigitsFilter : public Filter {
|
||||
}
|
||||
};
|
||||
|
||||
class ToNTCResistanceFilter : public Filter {
|
||||
public:
|
||||
ToNTCResistanceFilter(double a, double b, double c) : a_(a), b_(b), c_(c) {}
|
||||
optional<float> new_value(float value) override;
|
||||
/// Clamp to [min, max] for the clamp filter; a non-finite bound is no bound, and NaN input fails the first set bound.
|
||||
/// Out of range gives the bound, or empty to drop the value with `ignore_out_of_range`.
|
||||
optional<float> clamp_filter_value(float value, float min, float max, bool ignore_out_of_range);
|
||||
|
||||
protected:
|
||||
double a_;
|
||||
double b_;
|
||||
double c_;
|
||||
};
|
||||
|
||||
class ToNTCTemperatureFilter : public Filter {
|
||||
public:
|
||||
ToNTCTemperatureFilter(double a, double b, double c) : a_(a), b_(b), c_(c) {}
|
||||
optional<float> new_value(float value) override;
|
||||
|
||||
protected:
|
||||
double a_;
|
||||
double b_;
|
||||
double c_;
|
||||
};
|
||||
/// Steinhart-Hart conversions in double precision for the to_ntc_* filters; non-finite input gives NaN.
|
||||
float steinhart_hart_resistance(float temperature, double a, double b, double c);
|
||||
float steinhart_hart_temperature(float resistance, double a, double b, double c);
|
||||
|
||||
/** RH Temperature correction filter
|
||||
*
|
||||
|
||||
@@ -82,7 +82,6 @@ class Sensor : public EntityBase {
|
||||
*
|
||||
* sensor->add_filters({
|
||||
* LambdaFilter([&](float value) -> optional<float> { return 42/value; }),
|
||||
* OffsetFilter(1),
|
||||
* SlidingWindowMovingAverageFilter(15, 15), // average over last 15 values
|
||||
* });
|
||||
*/
|
||||
|
||||
@@ -51,13 +51,17 @@ static void SensorFilter_ExponentialMovingAvg(benchmark::State &state) {
|
||||
}
|
||||
BENCHMARK(SensorFilter_ExponentialMovingAvg);
|
||||
|
||||
// Mirror the code generated for `offset: 1` and `multiply: 2`
|
||||
static optional<float> offset_1(float x) { return x + 1.0f; }
|
||||
static optional<float> multiply_2(float x) { return x * 2.0f; }
|
||||
|
||||
// Benchmark: sensor publish through a chain of 3 filters (offset + multiply + sliding window)
|
||||
static void SensorFilter_Chain3(benchmark::State &state) {
|
||||
Sensor sensor;
|
||||
|
||||
sensor.add_filters({
|
||||
new OffsetFilter([]() -> float { return 1.0f; }),
|
||||
new MultiplyFilter([]() -> float { return 2.0f; }),
|
||||
new ApplyFilter<offset_1>(),
|
||||
new ApplyFilter<multiply_2>(),
|
||||
new SlidingWindowMovingAverageFilter(5, 1, 1),
|
||||
});
|
||||
|
||||
|
||||
@@ -33,6 +33,26 @@ sensor:
|
||||
- calibrate_linear:
|
||||
- 0.0 -> 1.0
|
||||
- 10.0 -> 21.0
|
||||
# Exact with one segment: two points, and collinear points that merge into one
|
||||
- platform: template
|
||||
id: exact_two_points
|
||||
lambda: return 1.0;
|
||||
filters:
|
||||
- calibrate_linear:
|
||||
method: exact
|
||||
datapoints:
|
||||
- 0.0 -> 3.0
|
||||
- 10.0 -> 23.0
|
||||
- platform: template
|
||||
id: exact_collinear
|
||||
lambda: return 1.0;
|
||||
filters:
|
||||
- calibrate_linear:
|
||||
method: exact
|
||||
datapoints:
|
||||
- 0.0 -> 5.0
|
||||
- 10.0 -> 35.0
|
||||
- 20.0 -> 65.0
|
||||
- platform: template
|
||||
id: polynomial
|
||||
lambda: return 1.0;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp8266:
|
||||
board: d1_mini_lite
|
||||
|
||||
sensor:
|
||||
# Bounds are compared as C++ floats: just beyond FLT_MAX is infinite there, so no bound
|
||||
- platform: template
|
||||
id: clamp_beyond_max
|
||||
lambda: return 1.0;
|
||||
filters:
|
||||
- clamp:
|
||||
min_value: 1.25
|
||||
max_value: 3.4028236e38
|
||||
- platform: template
|
||||
id: clamp_beyond_min
|
||||
lambda: return 1.0;
|
||||
filters:
|
||||
- clamp:
|
||||
min_value: -3.4028236e38
|
||||
max_value: 2.75
|
||||
ignore_out_of_range: true
|
||||
# 3.4028235e38 rounds to FLT_MAX, so it stays a bound
|
||||
- platform: template
|
||||
id: clamp_flt_max
|
||||
lambda: return 1.0;
|
||||
filters:
|
||||
- clamp:
|
||||
min_value: 0.5
|
||||
max_value: 3.4028235e38
|
||||
- platform: template
|
||||
id: clamp_negative_flt_max
|
||||
lambda: return 1.0;
|
||||
filters:
|
||||
- clamp:
|
||||
min_value: -3.4028235e38
|
||||
max_value: 5.5
|
||||
# Rounded by the compiler from the literal, not by Python first
|
||||
- platform: template
|
||||
id: clamp_float_midpoint
|
||||
lambda: return 1.0;
|
||||
filters:
|
||||
- clamp:
|
||||
min_value: 1.0000000596046448
|
||||
ignore_out_of_range: true
|
||||
@@ -29,10 +29,14 @@ def test_calibration_tables_are_shared_progmem(
|
||||
linear = re.findall(
|
||||
r"static constexpr std::array<float, 3> (\w+)\[\] PROGMEM", main_cpp
|
||||
)
|
||||
assert len(linear) == 2 # the two exact filters share one table
|
||||
exact, least_squares = linear
|
||||
assert len(linear) == 1 # only the exact filters have a table, and they share it
|
||||
(exact,) = linear
|
||||
assert main_cpp.count(f"sensor::CalibrateLinearFilter({exact});") == 2
|
||||
assert f"sensor::CalibrateLinearFilter({least_squares});" in main_cpp
|
||||
# A single segment needs no table: its function is baked into an apply filter
|
||||
assert "return x * 2.0f + 1.0f;" in main_cpp
|
||||
# So does an exact one, from two points or from collinear points
|
||||
assert "return x * 2.0f + 3.0f;" in main_cpp
|
||||
assert "return x * 3.0f + 5.0f;" in main_cpp
|
||||
|
||||
poly = re.search(
|
||||
r"static constexpr float (\w+)\[\] PROGMEM = \{2\.0f, 1\.0f, 2\.0f\};", main_cpp
|
||||
@@ -41,4 +45,17 @@ def test_calibration_tables_are_shared_progmem(
|
||||
assert f"sensor::CalibratePolynomialFilter({poly.group(1)});" in main_cpp
|
||||
# Table names never reuse a user id.
|
||||
assert exact != "sensor_calibrate_linear"
|
||||
assert "sensor_calibratelinearfilter_id_data" not in (exact, least_squares)
|
||||
assert exact != "sensor_calibratelinearfilter_id_data"
|
||||
|
||||
|
||||
def test_clamp_bounds_are_compared_as_float(
|
||||
generate_main: Callable[[str], str],
|
||||
) -> None:
|
||||
"""A bound beyond float range is no bound; one that rounds to FLT_MAX stays a bound."""
|
||||
main_cpp = generate_main("tests/component_tests/sensor/test_clamp.yaml")
|
||||
|
||||
assert "clamp_filter_value(x, 1.25f, NAN, false);" in main_cpp
|
||||
assert "clamp_filter_value(x, NAN, 2.75f, true);" in main_cpp
|
||||
assert "clamp_filter_value(x, 0.5f, 3.4028235e+38f, false);" in main_cpp
|
||||
assert "clamp_filter_value(x, -3.4028235e+38f, 5.5f, false);" in main_cpp
|
||||
assert "clamp_filter_value(x, 1.0000000596046448f, NAN, true);" in main_cpp
|
||||
@@ -212,9 +212,11 @@ TEST_F(ATM90E32ThresholdRegisters, RefreshesThresholdsAfterRestoringAndClearingS
|
||||
}
|
||||
#endif // USE_HOST
|
||||
|
||||
static optional<float> double_value(float value) { return value * 2.0f; }
|
||||
|
||||
TEST_F(ATM90E32ThresholdRegisters, CombinesNativeCurrentWithOtherPhaseAlarms) {
|
||||
sensor::Sensor current;
|
||||
sensor::MultiplyFilter multiply([]() { return 2.0f; });
|
||||
sensor::ApplyFilter<double_value> multiply;
|
||||
current.add_filter(&multiply);
|
||||
current.publish_state(6.0f);
|
||||
ASSERT_FLOAT_EQ(current.state, 12.0f);
|
||||
|
||||
Reference in new issue
Block a user