ESPHome 2026.6.0-dev
Loading...
Searching...
No Matches
combination.cpp
Go to the documentation of this file.
1#include "combination.h"
2
3#include "esphome/core/log.h"
4#include "esphome/core/hal.h"
5
6#include <cmath>
7
9
10static const char *const TAG = "combination";
11
12void CombinationComponent::log_config_(const LogString *combo_type) {
13 LOG_SENSOR("", "Combination Sensor:", this);
14 ESP_LOGCONFIG(TAG, " Combination Type: %s", LOG_STR_ARG(combo_type));
15 this->log_source_sensors();
16}
17
18void CombinationNoParameterComponent::add_source(Sensor *sensor) { this->sensors_.emplace_back(sensor); }
19
20void CombinationOneParameterComponent::add_source(Sensor *sensor, std::function<float(float)> const &compute) {
21 this->sensor_sources_.push_back({sensor, compute, this});
22}
23
24void CombinationOneParameterComponent::add_source(Sensor *sensor, float value) {
25 this->add_source(sensor, std::function<float(float)>{[value](float x) -> float { return value; }});
26}
27
29 ESP_LOGCONFIG(TAG, " Source Sensors:");
30 for (const auto &sensor : this->sensors_) {
31 ESP_LOGCONFIG(TAG, " - %s", sensor->get_name().c_str());
32 }
33}
34
36 ESP_LOGCONFIG(TAG, " Source Sensors:");
37 for (const auto &source : this->sensor_sources_) {
38 ESP_LOGCONFIG(TAG, " - %s", source.sensor->get_name().c_str());
39 }
40}
41
43 for (const auto &sensor : this->sensors_) {
44 // All sensor updates are deferred until the next loop. This avoids publishing the combined sensor's result
45 // repeatedly in the same loop if multiple source senors update.
46 sensor->add_on_state_callback(
47 [this](float value) -> void { this->defer("update", [this, value]() { this->handle_new_value(value); }); });
48 }
49}
50
52 this->log_config_(LOG_STR("kalman"));
53 ESP_LOGCONFIG(TAG, " Update variance: %f per ms", this->update_variance_value_);
54
55 if (this->std_dev_sensor_ != nullptr) {
56 LOG_SENSOR(" ", "Standard Deviation Sensor:", this->std_dev_sensor_);
57 }
58}
59
61 for (auto &source : this->sensor_sources_) {
62 // [&source] is safe: source refers to a FixedVector element that never reallocates,
63 // so the reference remains valid for the component's lifetime.
64 source.sensor->add_on_state_callback([&source](float x) -> void {
65 static_cast<KalmanCombinationComponent *>(source.parent)->correct_(x, source.compute(x));
66 });
67 }
68}
69
71 uint32_t now = millis();
72
73 // Variance increases by update_variance_ each millisecond
74 auto dt = now - this->last_update_;
75 auto dv = this->update_variance_value_ * dt;
76 this->variance_ += dv;
77 this->last_update_ = now;
78}
79
80void KalmanCombinationComponent::correct_(float value, float stddev) {
81 if (std::isnan(value) || std::isinf(stddev)) {
82 return;
83 }
84
85 if (std::isnan(this->state_) || std::isinf(this->variance_)) {
86 this->state_ = value;
87 this->variance_ = stddev * stddev;
88 if (this->std_dev_sensor_ != nullptr) {
89 this->std_dev_sensor_->publish_state(stddev);
90 }
91 return;
92 }
93
94 this->update_variance_();
95
96 // Combine two gaussian distributions mu1+-var1, mu2+-var2 to a new one around mu
97 // Use the value with the smaller variance as mu1 to prevent precision errors
98 const bool this_first = this->variance_ < (stddev * stddev);
99 const float mu1 = this_first ? this->state_ : value;
100 const float mu2 = this_first ? value : this->state_;
101
102 const float var1 = this_first ? this->variance_ : stddev * stddev;
103 const float var2 = this_first ? stddev * stddev : this->variance_;
104
105 const float mu = mu1 + var1 * (mu2 - mu1) / (var1 + var2);
106 const float var = var1 - (var1 * var1) / (var1 + var2);
107
108 // Update and publish state
109 this->state_ = mu;
110 this->variance_ = var;
111
112 this->publish_state(mu);
113 if (this->std_dev_sensor_ != nullptr) {
114 this->std_dev_sensor_->publish_state(std::sqrt(var));
115 }
116}
117
119 for (auto &source : this->sensor_sources_) {
120 // All sensor updates are deferred until the next loop. This avoids publishing the combined sensor's result
121 // repeatedly in the same loop if multiple source senors update.
122 source.sensor->add_on_state_callback(
123 [this](float value) -> void { this->defer("update", [this, value]() { this->handle_new_value(value); }); });
124 }
125}
126
128 // Multiplies each sensor state by a configured coefficient and then sums
129
130 if (!std::isfinite(value))
131 return;
132
133 float sum = 0.0;
134
135 for (const auto &source : this->sensor_sources_) {
136 const float sensor_state = source.sensor->state;
137 if (std::isfinite(sensor_state)) {
138 sum += sensor_state * source.compute(sensor_state);
139 }
140 }
141
142 this->publish_state(sum);
143};
144
146 if (!std::isfinite(value))
147 return;
148
149 float max_value = (-1) * std::numeric_limits<float>::infinity(); // note x = max(x, -infinity)
150
151 for (const auto &sensor : this->sensors_) {
152 if (std::isfinite(sensor->state)) {
153 max_value = std::max(max_value, sensor->state);
154 }
155 }
156
157 this->publish_state(max_value);
158}
159
161 if (!std::isfinite(value))
162 return;
163
164 float sum = 0.0;
165 size_t count = 0;
166
167 for (const auto &sensor : this->sensors_) {
168 if (std::isfinite(sensor->state)) {
169 ++count;
170 sum += sensor->state;
171 }
172 }
173
174 if (count == 0) {
175 this->publish_state(NAN);
176 return;
177 }
178 float mean = sum / count;
179
180 this->publish_state(mean);
181}
182
184 // Sorts sensor states in ascending order and determines the middle value
185
186 if (!std::isfinite(value))
187 return;
188
189 std::vector<float> sensor_states;
190 for (const auto &sensor : this->sensors_) {
191 if (std::isfinite(sensor->state)) {
192 sensor_states.push_back(sensor->state);
193 }
194 }
195
196 sort(sensor_states.begin(), sensor_states.end());
197 size_t sensor_states_size = sensor_states.size();
198
199 float median = NAN;
200
201 if (sensor_states_size) {
202 if (sensor_states_size % 2) {
203 // Odd number of measurements, use middle measurement
204 median = sensor_states[sensor_states_size / 2];
205 } else {
206 // Even number of measurements, use the average of the two middle measurements
207 median = (sensor_states[sensor_states_size / 2] + sensor_states[sensor_states_size / 2 - 1]) / 2.0;
208 }
209 }
210
211 this->publish_state(median);
212}
213
215 if (!std::isfinite(value))
216 return;
217
218 float min_value = std::numeric_limits<float>::infinity(); // note x = min(x, infinity)
219
220 for (const auto &sensor : this->sensors_) {
221 if (std::isfinite(sensor->state)) {
222 min_value = std::min(min_value, sensor->state);
223 }
224 }
225
226 this->publish_state(min_value);
227}
228
230
232 // Sorts sensor states then takes difference between largest and smallest states
233
234 if (!std::isfinite(value))
235 return;
236
237 std::vector<float> sensor_states;
238 for (const auto &sensor : this->sensors_) {
239 if (std::isfinite(sensor->state)) {
240 sensor_states.push_back(sensor->state);
241 }
242 }
243
244 if (sensor_states.empty()) {
245 this->publish_state(NAN);
246 return;
247 }
248
249 sort(sensor_states.begin(), sensor_states.end());
250
251 float range = sensor_states.back() - sensor_states.front();
252 this->publish_state(range);
253}
254
256 if (!std::isfinite(value))
257 return;
258
259 float sum = 0.0;
260 for (const auto &sensor : this->sensors_) {
261 if (std::isfinite(sensor->state)) {
262 sum += sensor->state;
263 }
264 }
265
266 this->publish_state(sum);
267}
268
269} // namespace esphome::combination
ESPDEPRECATED("Use const char* overload instead. Removed in 2026.7.0", "2026.1.0") void defer(const std voi defer)(const char *name, std::function< void()> &&f)
Defer a callback to the next loop() call.
Definition component.h:543
void log_config_(const LogString *combo_type)
Logs the sensor for use in dump_config.
virtual void log_source_sensors()=0
Logs all source sensor's names.
void setup() override
Adds a callback to each source sensor.
virtual void handle_new_value(float value)=0
Computes the combination.
void log_source_sensors() override
Logs all source sensor's names in sensors_.
void log_source_sensors() override
Logs all source sensors' names in sensor_sources_.
void add_source(Sensor *sensor, std::function< float(float)> const &compute)
void correct_(float value, float stddev)
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:68
Range range
Definition msa3xx.h:0
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:28
static void uint32_t
uint16_t x
Definition tt21100.cpp:5