ESPHome  2024.5.0
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 
10 namespace esphome {
11 namespace combination {
12 
13 static const char *const TAG = "combination";
14 
15 void 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 
21 void CombinationNoParameterComponent::add_source(Sensor *sensor) { this->sensors_.emplace_back(sensor); }
22 
23 void CombinationOneParameterComponent::add_source(Sensor *sensor, std::function<float(float)> const &stddev) {
24  this->sensor_pairs_.emplace_back(sensor, stddev);
25 }
26 
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 
81 void 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 coeffecient 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.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  float mean = sum / count;
176 
177  this->publish_state(mean);
178 }
179 
181  // Sorts sensor states in ascending order and determines the middle value
182 
183  if (!std::isfinite(value))
184  return;
185 
186  std::vector<float> sensor_states;
187  for (const auto &sensor : this->sensors_) {
188  if (std::isfinite(sensor->state)) {
189  sensor_states.push_back(sensor->state);
190  }
191  }
192 
193  sort(sensor_states.begin(), sensor_states.end());
194  size_t sensor_states_size = sensor_states.size();
195 
196  float median = NAN;
197 
198  if (sensor_states_size) {
199  if (sensor_states_size % 2) {
200  // Odd number of measurements, use middle measurement
201  median = sensor_states[sensor_states_size / 2];
202  } else {
203  // Even number of measurements, use the average of the two middle measurements
204  median = (sensor_states[sensor_states_size / 2] + sensor_states[sensor_states_size / 2 - 1]) / 2.0;
205  }
206  }
207 
208  this->publish_state(median);
209 }
210 
212  if (!std::isfinite(value))
213  return;
214 
215  float min_value = std::numeric_limits<float>::infinity(); // note x = min(x, infinity)
216 
217  for (const auto &sensor : this->sensors_) {
218  if (std::isfinite(sensor->state)) {
219  min_value = std::min(min_value, sensor->state);
220  }
221  }
222 
223  this->publish_state(min_value);
224 }
225 
227 
229  // Sorts sensor states then takes difference between largest and smallest states
230 
231  if (!std::isfinite(value))
232  return;
233 
234  std::vector<float> sensor_states;
235  for (const auto &sensor : this->sensors_) {
236  if (std::isfinite(sensor->state)) {
237  sensor_states.push_back(sensor->state);
238  }
239  }
240 
241  sort(sensor_states.begin(), sensor_states.end());
242 
243  float range = sensor_states.back() - sensor_states.front();
244  this->publish_state(range);
245 }
246 
248  if (!std::isfinite(value))
249  return;
250 
251  float sum = 0.0;
252  for (const auto &sensor : this->sensors_) {
253  if (std::isfinite(sensor->state)) {
254  sum += sensor->state;
255  }
256  }
257 
258  this->publish_state(sum);
259 }
260 
261 } // namespace combination
262 } // namespace esphome
void correct_(float value, float stddev)
Definition: combination.cpp:81
uint16_t x
Definition: tt21100.cpp:17
void log_config_(const LogString *combo_type)
Logs the sensor for use in dump_config.
Definition: combination.cpp:15
void defer(const std::string &name, std::function< void()> &&f)
Defer a callback to the next loop() call.
Definition: component.cpp:130
void handle_new_value(float value) override
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
void add_source(Sensor *sensor, std::function< float(float)> const &stddev)
Definition: combination.cpp:23
void setup() override
Adds a callback to each source sensor.
Definition: combination.cpp:46
void log_source_sensors() override
Logs all source sensor&#39;s names in sensors_.
Definition: combination.cpp:31
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
void log_source_sensors() override
Logs all source sensor&#39;s names in sensor_pairs_.
Definition: combination.cpp:38
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
virtual void log_source_sensors()=0
Logs all source sensor&#39;s names.