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