1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-26 04:33:47 +00:00

Merge branch 'integration' into memory_api

This commit is contained in:
J. Nick Koston
2025-10-21 11:16:06 -10:00
13 changed files with 157 additions and 21 deletions

View File

@@ -2,11 +2,11 @@
#include <cinttypes> #include <cinttypes>
#include <utility> #include <utility>
#include <vector>
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/components/binary_sensor/binary_sensor.h" #include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome { namespace esphome {
@@ -92,8 +92,8 @@ class DoubleClickTrigger : public Trigger<> {
class MultiClickTrigger : public Trigger<>, public Component { class MultiClickTrigger : public Trigger<>, public Component {
public: public:
explicit MultiClickTrigger(BinarySensor *parent, std::vector<MultiClickTriggerEvent> timing) explicit MultiClickTrigger(BinarySensor *parent, std::initializer_list<MultiClickTriggerEvent> timing)
: parent_(parent), timing_(std::move(timing)) {} : parent_(parent), timing_(timing) {}
void setup() override { void setup() override {
this->last_state_ = this->parent_->get_state_default(false); this->last_state_ = this->parent_->get_state_default(false);
@@ -115,7 +115,7 @@ class MultiClickTrigger : public Trigger<>, public Component {
void trigger_(); void trigger_();
BinarySensor *parent_; BinarySensor *parent_;
std::vector<MultiClickTriggerEvent> timing_; FixedVector<MultiClickTriggerEvent> timing_;
uint32_t invalid_cooldown_{1000}; uint32_t invalid_cooldown_{1000};
optional<size_t> at_index_{}; optional<size_t> at_index_{};
bool last_state_{false}; bool last_state_{false};

View File

@@ -62,7 +62,7 @@ void AddressableLightTransformer::start() {
} }
optional<LightColorValues> AddressableLightTransformer::apply() { optional<LightColorValues> AddressableLightTransformer::apply() {
float smoothed_progress = LightTransitionTransformer::smoothed_progress(this->get_progress_()); float smoothed_progress = LightTransformer::smoothed_progress(this->get_progress_());
// When running an output-buffer modifying effect, don't try to transition individual LEDs, but instead just fade the // When running an output-buffer modifying effect, don't try to transition individual LEDs, but instead just fade the
// LightColorValues. write_state() then picks up the change in brightness, and the color change is picked up by the // LightColorValues. write_state() then picks up the change in brightness, and the color change is picked up by the

View File

@@ -8,7 +8,7 @@
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "light_output.h" #include "light_output.h"
#include "light_state.h" #include "light_state.h"
#include "transformers.h" #include "light_transformer.h"
#ifdef USE_POWER_SUPPLY #ifdef USE_POWER_SUPPLY
#include "esphome/components/power_supply/power_supply.h" #include "esphome/components/power_supply/power_supply.h"
@@ -103,7 +103,7 @@ class AddressableLight : public LightOutput, public Component {
bool effect_active_{false}; bool effect_active_{false};
}; };
class AddressableLightTransformer : public LightTransitionTransformer { class AddressableLightTransformer : public LightTransformer {
public: public:
AddressableLightTransformer(AddressableLight &light) : light_(light) {} AddressableLightTransformer(AddressableLight &light) : light_(light) {}

View File

@@ -1,9 +1,9 @@
#pragma once #pragma once
#include <utility> #include <utility>
#include <vector>
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/components/light/light_state.h" #include "esphome/components/light/light_state.h"
#include "esphome/components/light/addressable_light.h" #include "esphome/components/light/addressable_light.h"
@@ -113,7 +113,7 @@ struct AddressableColorWipeEffectColor {
class AddressableColorWipeEffect : public AddressableLightEffect { class AddressableColorWipeEffect : public AddressableLightEffect {
public: public:
explicit AddressableColorWipeEffect(const std::string &name) : AddressableLightEffect(name) {} explicit AddressableColorWipeEffect(const std::string &name) : AddressableLightEffect(name) {}
void set_colors(const std::vector<AddressableColorWipeEffectColor> &colors) { this->colors_ = colors; } void set_colors(const std::initializer_list<AddressableColorWipeEffectColor> &colors) { this->colors_ = colors; }
void set_add_led_interval(uint32_t add_led_interval) { this->add_led_interval_ = add_led_interval; } void set_add_led_interval(uint32_t add_led_interval) { this->add_led_interval_ = add_led_interval; }
void set_reverse(bool reverse) { this->reverse_ = reverse; } void set_reverse(bool reverse) { this->reverse_ = reverse; }
void apply(AddressableLight &it, const Color &current_color) override { void apply(AddressableLight &it, const Color &current_color) override {
@@ -155,7 +155,7 @@ class AddressableColorWipeEffect : public AddressableLightEffect {
} }
protected: protected:
std::vector<AddressableColorWipeEffectColor> colors_; FixedVector<AddressableColorWipeEffectColor> colors_;
size_t at_color_{0}; size_t at_color_{0};
uint32_t last_add_{0}; uint32_t last_add_{0};
uint32_t add_led_interval_{}; uint32_t add_led_interval_{};

View File

@@ -1,9 +1,9 @@
#pragma once #pragma once
#include <utility> #include <utility>
#include <vector>
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/helpers.h"
#include "light_effect.h" #include "light_effect.h"
namespace esphome { namespace esphome {
@@ -188,10 +188,10 @@ class StrobeLightEffect : public LightEffect {
this->last_switch_ = now; this->last_switch_ = now;
} }
void set_colors(const std::vector<StrobeLightEffectColor> &colors) { this->colors_ = colors; } void set_colors(const std::initializer_list<StrobeLightEffectColor> &colors) { this->colors_ = colors; }
protected: protected:
std::vector<StrobeLightEffectColor> colors_; FixedVector<StrobeLightEffectColor> colors_;
uint32_t last_switch_{0}; uint32_t last_switch_{0};
size_t at_color_{0}; size_t at_color_{0};
}; };

View File

@@ -38,6 +38,10 @@ class LightTransformer {
const LightColorValues &get_target_values() const { return this->target_values_; } const LightColorValues &get_target_values() const { return this->target_values_; }
protected: protected:
// This looks crazy, but it reduces to 6x^5 - 15x^4 + 10x^3 which is just a smooth sigmoid-like
// transition from 0 to 1 on x = [0, 1]
static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); }
/// The progress of this transition, on a scale of 0 to 1. /// The progress of this transition, on a scale of 0 to 1.
float get_progress_() { float get_progress_() {
uint32_t now = esphome::millis(); uint32_t now = esphome::millis();

View File

@@ -50,15 +50,11 @@ class LightTransitionTransformer : public LightTransformer {
if (this->changing_color_mode_) if (this->changing_color_mode_)
p = p < 0.5f ? p * 2 : (p - 0.5) * 2; p = p < 0.5f ? p * 2 : (p - 0.5) * 2;
float v = LightTransitionTransformer::smoothed_progress(p); float v = LightTransformer::smoothed_progress(p);
return LightColorValues::lerp(start, end, v); return LightColorValues::lerp(start, end, v);
} }
protected: protected:
// This looks crazy, but it reduces to 6x^5 - 15x^4 + 10x^3 which is just a smooth sigmoid-like
// transition from 0 to 1 on x = [0, 1]
static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); }
LightColorValues end_values_{}; LightColorValues end_values_{};
LightColorValues intermediate_values_{}; LightColorValues intermediate_values_{};
bool changing_color_mode_{false}; bool changing_color_mode_{false};

View File

@@ -28,6 +28,8 @@ from esphome.const import (
CONF_ON_RAW_VALUE, CONF_ON_RAW_VALUE,
CONF_ON_VALUE, CONF_ON_VALUE,
CONF_ON_VALUE_RANGE, CONF_ON_VALUE_RANGE,
CONF_OPTIMISTIC,
CONF_PERIOD,
CONF_QUANTILE, CONF_QUANTILE,
CONF_SEND_EVERY, CONF_SEND_EVERY,
CONF_SEND_FIRST_AT, CONF_SEND_FIRST_AT,
@@ -644,10 +646,29 @@ async def throttle_with_priority_filter_to_code(config, filter_id):
return cg.new_Pvariable(filter_id, config[CONF_TIMEOUT], template_) return cg.new_Pvariable(filter_id, config[CONF_TIMEOUT], template_)
HEARTBEAT_SCHEMA = cv.Schema(
{
cv.Required(CONF_PERIOD): cv.positive_time_period_milliseconds,
cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
}
)
@FILTER_REGISTRY.register( @FILTER_REGISTRY.register(
"heartbeat", HeartbeatFilter, cv.positive_time_period_milliseconds "heartbeat",
HeartbeatFilter,
cv.Any(
cv.positive_time_period_milliseconds,
HEARTBEAT_SCHEMA,
),
) )
async def heartbeat_filter_to_code(config, filter_id): async def heartbeat_filter_to_code(config, filter_id):
if isinstance(config, dict):
var = cg.new_Pvariable(filter_id, config[CONF_PERIOD])
await cg.register_component(var, {})
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
return var
var = cg.new_Pvariable(filter_id, config) var = cg.new_Pvariable(filter_id, config)
await cg.register_component(var, {}) await cg.register_component(var, {})
return var return var

View File

@@ -372,8 +372,12 @@ optional<float> HeartbeatFilter::new_value(float value) {
this->last_input_ = value; this->last_input_ = value;
this->has_value_ = true; this->has_value_ = true;
if (this->optimistic_) {
return value;
}
return {}; return {};
} }
void HeartbeatFilter::setup() { void HeartbeatFilter::setup() {
this->set_interval("heartbeat", this->time_period_, [this]() { this->set_interval("heartbeat", this->time_period_, [this]() {
ESP_LOGVV(TAG, "HeartbeatFilter(%p)::interval(has_value=%s, last_input=%f)", this, YESNO(this->has_value_), ESP_LOGVV(TAG, "HeartbeatFilter(%p)::interval(has_value=%s, last_input=%f)", this, YESNO(this->has_value_),
@@ -384,6 +388,7 @@ void HeartbeatFilter::setup() {
this->output(this->last_input_); this->output(this->last_input_);
}); });
} }
float HeartbeatFilter::get_setup_priority() const { return setup_priority::HARDWARE; } float HeartbeatFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
CalibrateLinearFilter::CalibrateLinearFilter(std::initializer_list<std::array<float, 3>> linear_functions) CalibrateLinearFilter::CalibrateLinearFilter(std::initializer_list<std::array<float, 3>> linear_functions)

View File

@@ -396,15 +396,16 @@ class HeartbeatFilter : public Filter, public Component {
explicit HeartbeatFilter(uint32_t time_period); explicit HeartbeatFilter(uint32_t time_period);
void setup() override; void setup() override;
optional<float> new_value(float value) override; optional<float> new_value(float value) override;
float get_setup_priority() const override; float get_setup_priority() const override;
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
protected: protected:
uint32_t time_period_; uint32_t time_period_;
float last_input_; float last_input_;
bool has_value_{false}; bool has_value_{false};
bool optimistic_{false};
}; };
class DeltaFilter : public Filter { class DeltaFilter : public Filter {

View File

@@ -70,3 +70,69 @@ binary_sensor:
- delay: 10s - delay: 10s
time_off: 200ms time_off: 200ms
time_on: 800ms time_on: 800ms
# Test on_multi_click with single click
- platform: template
id: multi_click_single
name: "Multi Click Single"
on_multi_click:
- timing:
- state: true
min_length: 50ms
max_length: 350ms
then:
- logger.log: "Single click detected"
# Test on_multi_click with double click
- platform: template
id: multi_click_double
name: "Multi Click Double"
on_multi_click:
- timing:
- state: true
min_length: 50ms
max_length: 350ms
- state: false
min_length: 50ms
max_length: 350ms
- state: true
min_length: 50ms
max_length: 350ms
then:
- logger.log: "Double click detected"
# Test on_multi_click with complex pattern (5 events)
- platform: template
id: multi_click_complex
name: "Multi Click Complex"
on_multi_click:
- timing:
- state: true
min_length: 50ms
max_length: 350ms
- state: false
min_length: 50ms
max_length: 350ms
- state: true
min_length: 50ms
max_length: 350ms
- state: false
min_length: 50ms
max_length: 350ms
- state: true
min_length: 50ms
then:
- logger.log: "Complex pattern detected"
# Test on_multi_click with custom invalid_cooldown
- platform: template
id: multi_click_cooldown
name: "Multi Click Cooldown"
on_multi_click:
- timing:
- state: true
min_length: 100ms
max_length: 500ms
invalid_cooldown: 2s
then:
- logger.log: "Click with custom cooldown"

View File

@@ -123,3 +123,43 @@ light:
red: 100% red: 100%
green: 50% green: 50%
blue: 50% blue: 50%
# Test StrobeLightEffect with multiple colors
- platform: monochromatic
id: test_strobe_multiple
name: Strobe Multiple Colors
output: test_ledc_1
effects:
- strobe:
name: Strobe Multi
colors:
- state: true
brightness: 100%
duration: 500ms
- state: false
duration: 250ms
- state: true
brightness: 50%
duration: 500ms
# Test StrobeLightEffect with transition
- platform: rgb
id: test_strobe_transition
name: Strobe With Transition
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
effects:
- strobe:
name: Strobe Transition
colors:
- state: true
red: 100%
green: 0%
blue: 0%
duration: 1s
transition_length: 500ms
- state: true
red: 0%
green: 100%
blue: 0%
duration: 1s
transition_length: 500ms

View File

@@ -101,6 +101,9 @@ sensor:
- filter_out: 10 - filter_out: 10
- filter_out: !lambda return NAN; - filter_out: !lambda return NAN;
- heartbeat: 5s - heartbeat: 5s
- heartbeat:
period: 5s
optimistic: true
- lambda: return x * (9.0/5.0) + 32.0; - lambda: return x * (9.0/5.0) + 32.0;
- max: - max:
window_size: 10 window_size: 10