[combination] Store parent pointer to enable inline Callback storage

Replace std::pair<Sensor*, std::function> with a SensorSource struct
that includes a back-pointer to the parent component. This allows
KalmanCombinationComponent::setup() to capture only [&source] (one
pointer) instead of [this, stddev] (this + a full std::function copy),
enabling inline Callback storage and avoiding a heap allocation per
sensor callback registration.
This commit is contained in:
J. Nick Koston
2026-03-18 20:05:49 -10:00
parent 9d6f2f71e8
commit 5b8acaa7c0
2 changed files with 26 additions and 21 deletions
+15 -17
View File
@@ -4,8 +4,6 @@
#include "esphome/core/hal.h"
#include <cmath>
#include <functional>
#include <vector>
namespace esphome {
namespace combination {
@@ -20,12 +18,12 @@ void CombinationComponent::log_config_(const LogString *combo_type) {
void CombinationNoParameterComponent::add_source(Sensor *sensor) { this->sensors_.emplace_back(sensor); }
void CombinationOneParameterComponent::add_source(Sensor *sensor, std::function<float(float)> const &stddev) {
this->sensor_pairs_.emplace_back(sensor, stddev);
void CombinationOneParameterComponent::add_source(Sensor *sensor, std::function<float(float)> const &compute) {
this->sensor_sources_.push_back({sensor, compute, this});
}
void CombinationOneParameterComponent::add_source(Sensor *sensor, float stddev) {
this->add_source(sensor, std::function<float(float)>{[stddev](float x) -> float { return stddev; }});
void CombinationOneParameterComponent::add_source(Sensor *sensor, float value) {
this->add_source(sensor, std::function<float(float)>{[value](float x) -> float { return value; }});
}
void CombinationNoParameterComponent::log_source_sensors() {
@@ -37,9 +35,8 @@ void CombinationNoParameterComponent::log_source_sensors() {
void CombinationOneParameterComponent::log_source_sensors() {
ESP_LOGCONFIG(TAG, " Source Sensors:");
for (const auto &sensor : this->sensor_pairs_) {
auto &entity = *sensor.first;
ESP_LOGCONFIG(TAG, " - %s", entity.get_name().c_str());
for (const auto &source : this->sensor_sources_) {
ESP_LOGCONFIG(TAG, " - %s", source.sensor->get_name().c_str());
}
}
@@ -62,9 +59,10 @@ void KalmanCombinationComponent::dump_config() {
}
void KalmanCombinationComponent::setup() {
for (const auto &sensor : this->sensor_pairs_) {
const auto stddev = sensor.second;
sensor.first->add_on_state_callback([this, stddev](float x) -> void { this->correct_(x, stddev(x)); });
for (auto &source : this->sensor_sources_) {
source.sensor->add_on_state_callback([&source](float x) -> void {
static_cast<KalmanCombinationComponent *>(source.parent)->correct_(x, source.compute(x));
});
}
}
@@ -117,10 +115,10 @@ void KalmanCombinationComponent::correct_(float value, float stddev) {
}
void LinearCombinationComponent::setup() {
for (const auto &sensor : this->sensor_pairs_) {
for (auto &source : this->sensor_sources_) {
// All sensor updates are deferred until the next loop. This avoids publishing the combined sensor's result
// repeatedly in the same loop if multiple source senors update.
sensor.first->add_on_state_callback(
source.sensor->add_on_state_callback(
[this](float value) -> void { this->defer("update", [this, value]() { this->handle_new_value(value); }); });
}
}
@@ -133,10 +131,10 @@ void LinearCombinationComponent::handle_new_value(float value) {
float sum = 0.0;
for (const auto &sensor : this->sensor_pairs_) {
const float sensor_state = sensor.first->state;
for (const auto &source : this->sensor_sources_) {
const float sensor_state = source.sensor->state;
if (std::isfinite(sensor_state)) {
sum += sensor_state * sensor.second(sensor_state);
sum += sensor_state * source.compute(sensor_state);
}
}
+11 -4
View File
@@ -3,6 +3,7 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include <functional>
#include <vector>
namespace esphome {
@@ -41,14 +42,20 @@ class CombinationNoParameterComponent : public CombinationComponent {
// Base class for opertions that require one parameter to compute the combination
class CombinationOneParameterComponent : public CombinationComponent {
public:
void add_source(Sensor *sensor, std::function<float(float)> const &stddev);
void add_source(Sensor *sensor, float stddev);
void add_source(Sensor *sensor, std::function<float(float)> const &compute);
void add_source(Sensor *sensor, float value);
/// @brief Logs all source sensor's names in sensor_pairs_
/// @brief Logs all source sensor's names in sensor_sources_
void log_source_sensors() override;
struct SensorSource {
sensor::Sensor *sensor;
std::function<float(float)> compute;
CombinationOneParameterComponent *parent;
};
protected:
std::vector<std::pair<Sensor *, std::function<float(float)>>> sensor_pairs_;
std::vector<SensorSource> sensor_sources_;
};
class KalmanCombinationComponent : public CombinationOneParameterComponent {