mirror of
https://github.com/esphome/esphome.git
synced 2026-09-21 03:58:41 +00:00
Merge remote-tracking branch 'upstream/dev' into integration
This commit is contained in:
@@ -366,14 +366,14 @@ class MQTTJsonMessageTrigger : public Trigger<JsonObjectConst> {
|
||||
|
||||
class MQTTConnectTrigger : public Trigger<bool> {
|
||||
public:
|
||||
explicit MQTTConnectTrigger(MQTTClientComponent *&client) {
|
||||
explicit MQTTConnectTrigger(MQTTClientComponent *client) {
|
||||
client->set_on_connect([this](bool session_present) { this->trigger(session_present); });
|
||||
}
|
||||
};
|
||||
|
||||
class MQTTDisconnectTrigger : public Trigger<MQTTClientDisconnectReason> {
|
||||
public:
|
||||
explicit MQTTDisconnectTrigger(MQTTClientComponent *&client) {
|
||||
explicit MQTTDisconnectTrigger(MQTTClientComponent *client) {
|
||||
client->set_on_disconnect([this](MQTTClientDisconnectReason reason) { this->trigger(reason); });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -565,6 +565,29 @@ def new_variable(
|
||||
return obj
|
||||
|
||||
|
||||
def _extract_component_ns(type_str: str) -> str:
|
||||
"""Extract the component namespace from a fully-qualified C++ type string.
|
||||
|
||||
Strips leading ``esphome::`` and template arguments, then returns
|
||||
the first namespace segment. Falls back to ``"esphome"`` when the
|
||||
type has no namespace qualifier (after stripping templates).
|
||||
|
||||
Examples::
|
||||
|
||||
esphome::dsmr::Dsmr -> dsmr
|
||||
esphome::logger::Logger -> logger
|
||||
esphome::Automation<std::optional<bool>, std::optional<bool>> -> esphome
|
||||
Logger -> esphome
|
||||
"""
|
||||
bare = type_str.removeprefix("esphome::")
|
||||
# Strip template arguments before namespace extraction to avoid
|
||||
# matching :: inside template params (e.g. Automation<std::optional<bool>>)
|
||||
bare_no_template = bare.split("<", maxsplit=1)[0]
|
||||
if "::" in bare_no_template:
|
||||
return bare_no_template.split("::", maxsplit=1)[0].rstrip("_")
|
||||
return "esphome"
|
||||
|
||||
|
||||
def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj":
|
||||
"""Declare a new pointer variable in the code generation.
|
||||
|
||||
@@ -585,14 +608,7 @@ def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj":
|
||||
# to avoid heap fragmentation on embedded devices.
|
||||
the_type = id_.type
|
||||
# Extract component namespace from type for memory analysis attribution
|
||||
type_str = str(the_type)
|
||||
# Strip leading esphome:: to get the component namespace
|
||||
# e.g. esphome::dsmr::Dsmr -> dsmr, logger::Logger -> logger
|
||||
bare = type_str.removeprefix("esphome::")
|
||||
if "::" in bare:
|
||||
component_ns = bare.split("::", maxsplit=1)[0].rstrip("_")
|
||||
else:
|
||||
component_ns = "esphome"
|
||||
component_ns = _extract_component_ns(str(the_type))
|
||||
storage_name = f"{component_ns}__{id_.id}__pstorage"
|
||||
|
||||
# Declare aligned byte array for the object storage
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
from tests.testing_helpers import ComponentManifestOverride
|
||||
|
||||
|
||||
def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
manifest.enable_codegen()
|
||||
@@ -0,0 +1,61 @@
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome::binary_sensor::benchmarks {
|
||||
|
||||
static constexpr int kInnerIterations = 2000;
|
||||
|
||||
// Benchmark: publish_state with alternating values (forces state change every time)
|
||||
static void BinarySensorPublish_Alternating(benchmark::State &state) {
|
||||
BinarySensor sensor;
|
||||
|
||||
// First publish to establish initial state
|
||||
sensor.publish_initial_state(false);
|
||||
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
sensor.publish_state(i % 2 == 0);
|
||||
}
|
||||
benchmark::DoNotOptimize(sensor.state);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(BinarySensorPublish_Alternating);
|
||||
|
||||
// Benchmark: publish_state with same value (tests dedup fast path)
|
||||
static void BinarySensorPublish_NoChange(benchmark::State &state) {
|
||||
BinarySensor sensor;
|
||||
|
||||
sensor.publish_initial_state(true);
|
||||
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
sensor.publish_state(true);
|
||||
}
|
||||
benchmark::DoNotOptimize(sensor.state);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(BinarySensorPublish_NoChange);
|
||||
|
||||
// Benchmark: publish_state with a callback registered
|
||||
static void BinarySensorPublish_WithCallback(benchmark::State &state) {
|
||||
BinarySensor sensor;
|
||||
|
||||
int callback_count = 0;
|
||||
sensor.add_on_state_callback([&callback_count](bool) { callback_count++; });
|
||||
|
||||
sensor.publish_initial_state(false);
|
||||
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
sensor.publish_state(i % 2 == 0);
|
||||
}
|
||||
benchmark::DoNotOptimize(callback_count);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(BinarySensorPublish_WithCallback);
|
||||
|
||||
} // namespace esphome::binary_sensor::benchmarks
|
||||
@@ -0,0 +1 @@
|
||||
binary_sensor:
|
||||
@@ -0,0 +1,12 @@
|
||||
import esphome.codegen as cg
|
||||
from tests.testing_helpers import ComponentManifestOverride
|
||||
|
||||
|
||||
def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
# Sensor filter benchmarks need USE_SENSOR_FILTER defined.
|
||||
# We use a custom to_code instead of enable_codegen() to avoid
|
||||
# pulling in the full sensor component setup.
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_SENSOR_FILTER")
|
||||
|
||||
manifest.to_code = to_code
|
||||
@@ -0,0 +1,78 @@
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/sensor/filter.h"
|
||||
|
||||
namespace esphome::sensor::benchmarks {
|
||||
|
||||
static constexpr int kInnerIterations = 2000;
|
||||
|
||||
// Benchmark: sensor publish through a SlidingWindowMovingAverageFilter (window=5, send_every=1)
|
||||
static void SensorFilter_SlidingWindowAvg(benchmark::State &state) {
|
||||
Sensor sensor;
|
||||
|
||||
// Create filter: window_size=5, send_every=1, send_first_at=1
|
||||
auto *filter = new SlidingWindowMovingAverageFilter(5, 1, 1);
|
||||
sensor.add_filter(filter);
|
||||
|
||||
float value = 0.0f;
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
sensor.publish_state(value);
|
||||
value += 0.1f;
|
||||
if (value > 1000.0f)
|
||||
value = 0.0f;
|
||||
}
|
||||
benchmark::DoNotOptimize(sensor.state);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(SensorFilter_SlidingWindowAvg);
|
||||
|
||||
// Benchmark: sensor publish through ExponentialMovingAverageFilter
|
||||
static void SensorFilter_ExponentialMovingAvg(benchmark::State &state) {
|
||||
Sensor sensor;
|
||||
|
||||
// alpha=0.1, send_every=1, send_first_at=1
|
||||
auto *filter = new ExponentialMovingAverageFilter(0.1f, 1, 1);
|
||||
sensor.add_filter(filter);
|
||||
|
||||
float value = 0.0f;
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
sensor.publish_state(value);
|
||||
value += 0.1f;
|
||||
if (value > 1000.0f)
|
||||
value = 0.0f;
|
||||
}
|
||||
benchmark::DoNotOptimize(sensor.state);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(SensorFilter_ExponentialMovingAvg);
|
||||
|
||||
// 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(1.0f),
|
||||
new MultiplyFilter(2.0f),
|
||||
new SlidingWindowMovingAverageFilter(5, 1, 1),
|
||||
});
|
||||
|
||||
float value = 0.0f;
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
sensor.publish_state(value);
|
||||
value += 0.1f;
|
||||
if (value > 1000.0f)
|
||||
value = 0.0f;
|
||||
}
|
||||
benchmark::DoNotOptimize(sensor.state);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(SensorFilter_Chain3);
|
||||
|
||||
} // namespace esphome::sensor::benchmarks
|
||||
@@ -0,0 +1 @@
|
||||
sensor:
|
||||
@@ -1,6 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from esphome import codegen as cg
|
||||
from esphome.cpp_generator import _extract_component_ns
|
||||
|
||||
|
||||
# Test interface remains the same.
|
||||
@@ -75,3 +76,29 @@ from esphome import codegen as cg
|
||||
)
|
||||
def test_exists(attr):
|
||||
assert hasattr(cg, attr)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("type_str", "expected"),
|
||||
(
|
||||
("esphome::dsmr::Dsmr", "dsmr"),
|
||||
("esphome::logger::Logger", "logger"),
|
||||
("esphome::web_server::WebServer", "web_server"),
|
||||
("esphome::deep_sleep::DeepSleep", "deep_sleep"),
|
||||
("esphome::Component", "esphome"),
|
||||
("Logger", "esphome"),
|
||||
# Template types with :: in template args must not confuse extraction
|
||||
(
|
||||
"esphome::Automation<std::optional<bool>, std::optional<bool>>",
|
||||
"esphome",
|
||||
),
|
||||
(
|
||||
"esphome::StatelessLambdaAction<std::optional<bool>, std::optional<bool>>",
|
||||
"esphome",
|
||||
),
|
||||
# Namespaced template type
|
||||
("esphome::sensor::Sensor<std::string>", "sensor"),
|
||||
),
|
||||
)
|
||||
def test_extract_component_ns(type_str, expected):
|
||||
assert _extract_component_ns(type_str) == expected
|
||||
|
||||
Reference in New Issue
Block a user