mirror of
https://github.com/esphome/esphome.git
synced 2026-09-20 19:48:39 +00:00
Merge branch 'scheduler-cancel-fast-path' into integration
This commit is contained in:
@@ -177,7 +177,7 @@ async def at581x_settings_to_code(config, action_id, template_arg, args):
|
||||
template_ = int(template_ / 1000000)
|
||||
cg.add(var.set_frequency(template_))
|
||||
|
||||
if sens_dist := config.get(CONF_SENSING_DISTANCE):
|
||||
if (sens_dist := config.get(CONF_SENSING_DISTANCE)) is not None:
|
||||
template_ = await cg.templatable(sens_dist, args, int)
|
||||
cg.add(var.set_sensing_distance(template_))
|
||||
|
||||
@@ -209,7 +209,7 @@ async def at581x_settings_to_code(config, action_id, template_arg, args):
|
||||
template_ = int(template_)
|
||||
cg.add(var.set_trigger_keep(template_))
|
||||
|
||||
if stage_gain := config.get(CONF_STAGE_GAIN):
|
||||
if (stage_gain := config.get(CONF_STAGE_GAIN)) is not None:
|
||||
template_ = await cg.templatable(stage_gain, args, int)
|
||||
cg.add(var.set_stage_gain(template_))
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ async def canbus_action_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_CANBUS_ID])
|
||||
|
||||
if can_id := config.get(CONF_CAN_ID):
|
||||
if (can_id := config.get(CONF_CAN_ID)) is not None:
|
||||
can_id = await cg.templatable(can_id, args, cg.uint32)
|
||||
cg.add(var.set_can_id(can_id))
|
||||
cg.add(var.set_use_extended_id(config[CONF_USE_EXTENDED_ID]))
|
||||
|
||||
@@ -150,10 +150,12 @@ void Nextion::reset_(bool reset_nextion) {
|
||||
delete entry; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
this->nextion_queue_.clear();
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
for (auto *entry : this->waveform_queue_) {
|
||||
delete entry; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
this->waveform_queue_.clear();
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
}
|
||||
|
||||
void Nextion::dump_config() {
|
||||
@@ -496,20 +498,21 @@ void Nextion::process_nextion_commands_() {
|
||||
ESP_LOGW(TAG, "Invalid baud rate");
|
||||
break;
|
||||
case 0x12: // invalid Waveform ID or Channel # was used
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
if (this->waveform_queue_.empty()) {
|
||||
ESP_LOGW(TAG, "Waveform ID/ch used but no sensor queued");
|
||||
} else {
|
||||
auto &nb = this->waveform_queue_.front();
|
||||
NextionComponentBase *component = nb->component;
|
||||
|
||||
ESP_LOGW(TAG, "Invalid waveform ID %d/ch %d", component->get_component_id(),
|
||||
component->get_wave_channel_id());
|
||||
|
||||
ESP_LOGN(TAG, "Remove waveform ID %d/ch %d", component->get_component_id(), component->get_wave_channel_id());
|
||||
|
||||
delete nb; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->waveform_queue_.pop_front();
|
||||
this->waveform_queue_.pop();
|
||||
}
|
||||
#else // USE_NEXTION_WAVEFORM
|
||||
ESP_LOGW(TAG, "Waveform ID/ch error but waveform not enabled");
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
break;
|
||||
case 0x1A: // variable name invalid
|
||||
ESP_LOGW(TAG, "Invalid variable name");
|
||||
@@ -812,29 +815,30 @@ void Nextion::process_nextion_commands_() {
|
||||
}
|
||||
case 0xFD: { // data transparent transmit finished
|
||||
ESP_LOGVV(TAG, "Data transmit done");
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
this->check_pending_waveform_();
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
break;
|
||||
}
|
||||
case 0xFE: { // data transparent transmit ready
|
||||
ESP_LOGVV(TAG, "Ready for transmit");
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
if (this->waveform_queue_.empty()) {
|
||||
ESP_LOGE(TAG, "No waveforms queued");
|
||||
break;
|
||||
}
|
||||
|
||||
auto &nb = this->waveform_queue_.front();
|
||||
auto *component = nb->component;
|
||||
size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size()
|
||||
: 255; // ADDT command can only send 255
|
||||
|
||||
size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size() : 255;
|
||||
this->write_array(component->get_wave_buffer().data(), static_cast<int>(buffer_to_send));
|
||||
|
||||
ESP_LOGN(TAG, "Send waveform: component id %d, waveform id %d, size %zu", component->get_component_id(),
|
||||
component->get_wave_channel_id(), buffer_to_send);
|
||||
|
||||
component->clear_wave_buffer(buffer_to_send);
|
||||
delete nb; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->waveform_queue_.pop_front();
|
||||
this->waveform_queue_.pop();
|
||||
#else // USE_NEXTION_WAVEFORM
|
||||
ESP_LOGW(TAG, "Waveform transmit ready but waveform not enabled");
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -934,8 +938,13 @@ void Nextion::all_components_send_state_(bool force_update) {
|
||||
binarysensortype->send_state_to_nextion();
|
||||
}
|
||||
for (auto *sensortype : this->sensortype_) {
|
||||
if ((force_update || sensortype->get_needs_to_send_update()) && sensortype->get_wave_channel_id() == 0)
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
if ((force_update || sensortype->get_needs_to_send_update()) && sensortype->get_wave_channel_id() == UINT8_MAX) {
|
||||
#else // USE_NEXTION_WAVEFORM
|
||||
if (force_update || sensortype->get_needs_to_send_update()) {
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
sensortype->send_state_to_nextion();
|
||||
}
|
||||
}
|
||||
for (auto *switchtype : this->switchtype_) {
|
||||
if (force_update || switchtype->get_needs_to_send_update())
|
||||
@@ -1239,13 +1248,11 @@ void Nextion::add_to_get_queue(NextionComponentBase *component) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
/**
|
||||
* @brief Add addt command to the queue
|
||||
* @brief Add addt command to the waveform queue.
|
||||
*
|
||||
* @param component_id The waveform component id
|
||||
* @param wave_chan_id The waveform channel to send it to
|
||||
* @param buffer_to_send The buffer size
|
||||
* @param buffer_size The buffer data
|
||||
* @param component Pointer to the Nextion component with waveform data to send.
|
||||
*/
|
||||
void Nextion::add_addt_command_to_queue(NextionComponentBase *component) {
|
||||
if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_) || this->is_sleeping())
|
||||
@@ -1262,7 +1269,11 @@ void Nextion::add_addt_command_to_queue(NextionComponentBase *component) {
|
||||
nextion_queue->component = component;
|
||||
nextion_queue->queue_time = App.get_loop_component_start_time();
|
||||
|
||||
this->waveform_queue_.push_back(nextion_queue);
|
||||
if (!this->waveform_queue_.push(nextion_queue)) {
|
||||
ESP_LOGW(TAG, "Waveform queue full, drop");
|
||||
delete nextion_queue; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
return;
|
||||
}
|
||||
if (this->waveform_queue_.size() == 1)
|
||||
this->check_pending_waveform_();
|
||||
}
|
||||
@@ -1273,17 +1284,17 @@ void Nextion::check_pending_waveform_() {
|
||||
|
||||
auto *nb = this->waveform_queue_.front();
|
||||
auto *component = nb->component;
|
||||
size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size()
|
||||
: 255; // ADDT command can only send 255
|
||||
size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size() : 255;
|
||||
|
||||
char command[24]; // "addt " + uint8 + "," + uint8 + "," + uint8 + null = max 17 chars
|
||||
buf_append_printf(command, sizeof(command), 0, "addt %u,%u,%zu", component->get_component_id(),
|
||||
component->get_wave_channel_id(), buffer_to_send);
|
||||
if (!this->send_command_(command)) {
|
||||
delete nb; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->waveform_queue_.pop_front();
|
||||
this->waveform_queue_.pop();
|
||||
}
|
||||
}
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
|
||||
void Nextion::set_writer(const nextion_writer_t &writer) { this->writer_ = writer; }
|
||||
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/time.h"
|
||||
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
#include "esphome/core/helpers.h"
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
|
||||
#include "nextion_base.h"
|
||||
#include "nextion_component.h"
|
||||
|
||||
@@ -602,6 +606,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
||||
*/
|
||||
void disable_component_touch(const char *component);
|
||||
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
/**
|
||||
* Add waveform data to a waveform component
|
||||
* @param component_id The integer component id.
|
||||
@@ -611,6 +616,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
||||
void add_waveform_data(uint8_t component_id, uint8_t channel_number, uint8_t value);
|
||||
|
||||
void open_waveform_channel(uint8_t component_id, uint8_t channel_number, uint8_t value);
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
|
||||
/**
|
||||
* Display a picture at coordinates.
|
||||
@@ -1205,7 +1211,9 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
||||
|
||||
void add_to_get_queue(NextionComponentBase *component) override;
|
||||
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
void add_addt_command_to_queue(NextionComponentBase *component) override;
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
|
||||
void update_components_by_prefix(const std::string &prefix);
|
||||
|
||||
@@ -1391,7 +1399,11 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
||||
#endif // USE_NEXTION_COMMAND_SPACING
|
||||
|
||||
std::list<NextionQueue *> nextion_queue_;
|
||||
std::list<NextionQueue *> waveform_queue_;
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
/// Fixed-size ring buffer for waveform queue. Nextion supports at most 4 waveform
|
||||
/// channels (IDs 0-3), so 4 entries is both the correct maximum and a safe default.
|
||||
StaticRingBuffer<NextionQueue *, 4> waveform_queue_;
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag);
|
||||
void all_components_send_state_(bool force_update = false);
|
||||
uint32_t comok_sent_ = 0;
|
||||
@@ -1460,7 +1472,9 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
||||
const std::string &variable_name_to_send,
|
||||
const std::string &state_value, bool is_sleep_safe = false);
|
||||
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
void check_pending_waveform_();
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
|
||||
#ifdef USE_NEXTION_TFT_UPLOAD
|
||||
#ifdef USE_ESP8266
|
||||
|
||||
@@ -33,7 +33,9 @@ class NextionBase {
|
||||
const std::string &variable_name_to_send,
|
||||
const std::string &state_value) = 0;
|
||||
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
virtual void add_addt_command_to_queue(NextionComponentBase *component) = 0;
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
|
||||
virtual void add_to_get_queue(NextionComponentBase *component) = 0;
|
||||
|
||||
|
||||
@@ -217,6 +217,7 @@ void Nextion::set_component_value(const char *component, int32_t value) {
|
||||
this->add_no_result_to_queue_with_printf_(".val", "%s.val=%" PRId32, component, value);
|
||||
}
|
||||
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
void Nextion::add_waveform_data(uint8_t component_id, uint8_t channel_number, uint8_t value) {
|
||||
this->add_no_result_to_queue_with_printf_("add", "add %" PRIu8 ",%" PRIu8 ",%" PRIu8, component_id, channel_number,
|
||||
value);
|
||||
@@ -226,6 +227,7 @@ void Nextion::open_waveform_channel(uint8_t component_id, uint8_t channel_number
|
||||
this->add_no_result_to_queue_with_printf_("addt", "addt %" PRIu8 ",%" PRIu8 ",%" PRIu8, component_id, channel_number,
|
||||
value);
|
||||
}
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
|
||||
void Nextion::set_component_coordinates(const char *component, uint16_t x, uint16_t y) {
|
||||
this->add_no_result_to_queue_with_printf_(".xcen", "%s.xcen=%" PRIu16, component, x);
|
||||
|
||||
@@ -64,6 +64,7 @@ class NextionComponentBase {
|
||||
uint8_t get_component_id() const { return this->component_id_; }
|
||||
void set_component_id(uint8_t component_id) { this->component_id_ = component_id; }
|
||||
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
uint8_t get_wave_channel_id() const { return this->wave_chan_id_; }
|
||||
void set_wave_channel_id(uint8_t wave_chan_id) { this->wave_chan_id_ = wave_chan_id; }
|
||||
|
||||
@@ -76,6 +77,7 @@ class NextionComponentBase {
|
||||
this->wave_buffer_.erase(this->wave_buffer_.begin(), this->wave_buffer_.begin() + buffer_sent);
|
||||
}
|
||||
}
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
|
||||
const std::string &get_variable_name() const { return this->variable_name_; }
|
||||
const std::string &get_variable_name_to_send() const { return this->variable_name_to_send_; }
|
||||
@@ -85,19 +87,23 @@ class NextionComponentBase {
|
||||
virtual void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion){};
|
||||
virtual void send_state_to_nextion(){};
|
||||
bool get_needs_to_send_update() const { return this->needs_to_send_update_; }
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
// Remove before 2026.10.0
|
||||
ESPDEPRECATED("Use get_wave_channel_id() instead. Will be removed in 2026.10.0", "2026.4.0")
|
||||
uint8_t get_wave_chan_id() const { return this->get_wave_channel_id(); }
|
||||
void set_wave_max_length(int wave_max_length) { this->wave_max_length_ = wave_max_length; }
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
|
||||
protected:
|
||||
std::string variable_name_;
|
||||
std::string variable_name_to_send_;
|
||||
|
||||
uint8_t component_id_ = 0;
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
uint8_t wave_chan_id_ = UINT8_MAX;
|
||||
std::vector<uint8_t> wave_buffer_;
|
||||
int wave_max_length_ = 255;
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
|
||||
bool needs_to_send_update_;
|
||||
};
|
||||
|
||||
@@ -85,16 +85,16 @@ async def to_code(config):
|
||||
cg.add(var.set_component_id(config[CONF_COMPONENT_ID]))
|
||||
|
||||
if CONF_WAVE_CHANNEL_ID in config:
|
||||
cg.add_define("USE_NEXTION_WAVEFORM")
|
||||
cg.add(var.set_wave_channel_id(config[CONF_WAVE_CHANNEL_ID]))
|
||||
|
||||
if CONF_WAVEFORM_SEND_LAST_VALUE in config:
|
||||
cg.add(var.set_waveform_send_last_value(config[CONF_WAVEFORM_SEND_LAST_VALUE]))
|
||||
|
||||
if CONF_WAVE_MAX_VALUE in config:
|
||||
cg.add(var.set_wave_max_value(config[CONF_WAVE_MAX_VALUE]))
|
||||
|
||||
if CONF_WAVE_MAX_LENGTH in config:
|
||||
cg.add(var.set_wave_max_length(config[CONF_WAVE_MAX_LENGTH]))
|
||||
if CONF_WAVEFORM_SEND_LAST_VALUE in config:
|
||||
cg.add(
|
||||
var.set_waveform_send_last_value(config[CONF_WAVEFORM_SEND_LAST_VALUE])
|
||||
)
|
||||
if CONF_WAVE_MAX_VALUE in config:
|
||||
cg.add(var.set_wave_max_value(config[CONF_WAVE_MAX_VALUE]))
|
||||
if CONF_WAVE_MAX_LENGTH in config:
|
||||
cg.add(var.set_wave_max_length(config[CONF_WAVE_MAX_LENGTH]))
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
|
||||
@@ -10,37 +10,44 @@ void NextionSensor::process_sensor(const std::string &variable_name, int state)
|
||||
if (!this->nextion_->is_setup())
|
||||
return;
|
||||
|
||||
if (this->wave_chan_id_ == UINT8_MAX && this->variable_name_ == variable_name) {
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
if (this->wave_chan_id_ == UINT8_MAX && this->variable_name_ == variable_name)
|
||||
#else // USE_NEXTION_WAVEFORM
|
||||
if (this->variable_name_ == variable_name)
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
{
|
||||
this->publish_state(state);
|
||||
ESP_LOGD(TAG, "Sensor: %s=%d", variable_name.c_str(), state);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
void NextionSensor::add_to_wave_buffer(float state) {
|
||||
this->needs_to_send_update_ = true;
|
||||
|
||||
int wave_state = (int) ((state / (float) this->wave_maxvalue_) * 100);
|
||||
|
||||
wave_buffer_.push_back(wave_state);
|
||||
|
||||
this->wave_buffer_.push_back(wave_state);
|
||||
if (this->wave_buffer_.size() > (size_t) this->wave_max_length_) {
|
||||
this->wave_buffer_.erase(this->wave_buffer_.begin());
|
||||
}
|
||||
}
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
|
||||
void NextionSensor::update() {
|
||||
if (!this->nextion_->is_setup() || this->nextion_->is_updating())
|
||||
return;
|
||||
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
if (this->wave_chan_id_ == UINT8_MAX) {
|
||||
this->nextion_->add_to_get_queue(this);
|
||||
} else {
|
||||
if (this->send_last_value_) {
|
||||
this->add_to_wave_buffer(this->last_value_);
|
||||
}
|
||||
|
||||
this->wave_update_();
|
||||
}
|
||||
#else // USE_NEXTION_WAVEFORM
|
||||
this->nextion_->add_to_get_queue(this);
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
}
|
||||
|
||||
void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) {
|
||||
@@ -50,61 +57,60 @@ void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) {
|
||||
if (std::isnan(state))
|
||||
return;
|
||||
|
||||
if (this->wave_chan_id_ == UINT8_MAX) {
|
||||
if (send_to_nextion) {
|
||||
if (this->nextion_->is_sleeping() || !this->component_flags_.visible) {
|
||||
this->needs_to_send_update_ = true;
|
||||
} else {
|
||||
this->needs_to_send_update_ = false;
|
||||
|
||||
if (this->precision_ > 0) {
|
||||
double to_multiply = pow(10, this->precision_);
|
||||
int state_value = (int) (state * to_multiply);
|
||||
|
||||
this->nextion_->add_no_result_to_queue_with_set(this, (int) state_value);
|
||||
} else {
|
||||
this->nextion_->add_no_result_to_queue_with_set(this, (int) state);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
if (this->wave_chan_id_ != UINT8_MAX) {
|
||||
// Waveform sensor — buffer the value, don't send directly.
|
||||
if (this->send_last_value_) {
|
||||
this->last_value_ = state; // Update will handle setting the buffer
|
||||
} else {
|
||||
this->add_to_wave_buffer(state);
|
||||
}
|
||||
this->update_component_settings();
|
||||
return;
|
||||
}
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
|
||||
if (send_to_nextion) {
|
||||
if (this->nextion_->is_sleeping() || !this->component_flags_.visible) {
|
||||
this->needs_to_send_update_ = true;
|
||||
} else {
|
||||
this->needs_to_send_update_ = false;
|
||||
if (this->precision_ > 0) {
|
||||
double to_multiply = pow(10, this->precision_);
|
||||
int state_value = (int) (state * to_multiply);
|
||||
this->nextion_->add_no_result_to_queue_with_set(this, (int) state_value);
|
||||
} else {
|
||||
this->nextion_->add_no_result_to_queue_with_set(this, (int) state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float published_state = state;
|
||||
if (this->wave_chan_id_ == UINT8_MAX) {
|
||||
if (publish) {
|
||||
if (this->precision_ > 0) {
|
||||
double to_multiply = pow(10, -this->precision_);
|
||||
published_state = (float) (state * to_multiply);
|
||||
}
|
||||
|
||||
this->publish_state(published_state);
|
||||
if (publish) {
|
||||
if (this->precision_ > 0) {
|
||||
double to_multiply = pow(10, -this->precision_);
|
||||
published_state = (float) (state * to_multiply);
|
||||
}
|
||||
this->publish_state(published_state);
|
||||
}
|
||||
this->update_component_settings();
|
||||
|
||||
ESP_LOGN(TAG, "Write: %s=%lf", this->variable_name_.c_str(), published_state);
|
||||
}
|
||||
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
void NextionSensor::wave_update_() {
|
||||
if (this->nextion_->is_sleeping() || this->wave_buffer_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef NEXTION_PROTOCOL_LOG
|
||||
size_t buffer_to_send =
|
||||
this->wave_buffer_.size() < 255 ? this->wave_buffer_.size() : 255; // ADDT command can only send 255
|
||||
|
||||
ESP_LOGN(TAG, "Wave update: %zu/%zu vals to comp %d ch %d", buffer_to_send, this->wave_buffer_.size(),
|
||||
this->component_id_, this->wave_chan_id_);
|
||||
#endif
|
||||
|
||||
#endif // NEXTION_PROTOCOL_LOG
|
||||
this->nextion_->add_addt_command_to_queue(this);
|
||||
}
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
|
||||
} // namespace esphome::nextion
|
||||
|
||||
@@ -15,22 +15,30 @@ class NextionSensor : public NextionComponent, public sensor::Sensor, public Pol
|
||||
|
||||
void update_component() override { this->update(); }
|
||||
void update() override;
|
||||
void add_to_wave_buffer(float state);
|
||||
void set_precision(uint8_t precision) { this->precision_ = precision; }
|
||||
void set_component_id(uint8_t component_id) { this->component_id_ = component_id; }
|
||||
void set_wave_channel_id(uint8_t wave_chan_id) { this->wave_chan_id_ = wave_chan_id; }
|
||||
void set_wave_max_value(uint32_t wave_maxvalue) { this->wave_maxvalue_ = wave_maxvalue; }
|
||||
void process_sensor(const std::string &variable_name, int state) override;
|
||||
|
||||
void set_state(float state) override { this->set_state(state, true, true); }
|
||||
void set_state(float state, bool publish) override { this->set_state(state, publish, true); }
|
||||
void set_state(float state, bool publish, bool send_to_nextion) override;
|
||||
|
||||
NextionQueueType get_queue_type() const override {
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
return this->wave_chan_id_ == UINT8_MAX ? NextionQueueType::SENSOR : NextionQueueType::WAVEFORM_SENSOR;
|
||||
#else // USE_NEXTION_WAVEFORM
|
||||
return NextionQueueType::SENSOR;
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
}
|
||||
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
void add_to_wave_buffer(float state);
|
||||
void set_wave_channel_id(uint8_t wave_chan_id) { this->wave_chan_id_ = wave_chan_id; }
|
||||
void set_wave_max_value(uint32_t wave_maxvalue) { this->wave_maxvalue_ = wave_maxvalue; }
|
||||
void set_waveform_send_last_value(bool send_last_value) { this->send_last_value_ = send_last_value; }
|
||||
void set_wave_max_length(int wave_max_length) { this->wave_max_length_ = wave_max_length; }
|
||||
NextionQueueType get_queue_type() const override {
|
||||
return this->wave_chan_id_ == UINT8_MAX ? NextionQueueType::SENSOR : NextionQueueType::WAVEFORM_SENSOR;
|
||||
}
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
|
||||
void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {}
|
||||
void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override {
|
||||
this->set_state(state_value, publish, send_to_nextion);
|
||||
@@ -38,10 +46,11 @@ class NextionSensor : public NextionComponent, public sensor::Sensor, public Pol
|
||||
|
||||
protected:
|
||||
uint8_t precision_ = 0;
|
||||
#ifdef USE_NEXTION_WAVEFORM
|
||||
uint32_t wave_maxvalue_ = 255;
|
||||
|
||||
float last_value_ = 0;
|
||||
bool send_last_value_ = true;
|
||||
void wave_update_();
|
||||
#endif // USE_NEXTION_WAVEFORM
|
||||
};
|
||||
} // namespace esphome::nextion
|
||||
|
||||
@@ -25,7 +25,6 @@ from esphome.const import (
|
||||
CONF_TEMPERATURE_COMPENSATION,
|
||||
CONF_TIME_CONSTANT,
|
||||
CONF_VOC,
|
||||
CONF_VOC_BASELINE,
|
||||
DEVICE_CLASS_AQI,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_PM1,
|
||||
@@ -165,7 +164,6 @@ CONFIG_SCHEMA = (
|
||||
gain_factor=230,
|
||||
),
|
||||
cv.Optional(CONF_STORE_BASELINE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_VOC_BASELINE): cv.hex_uint16_t,
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
#define USE_NEXTION_MAX_COMMANDS_PER_LOOP
|
||||
#define USE_NEXTION_MAX_QUEUE_SIZE
|
||||
#define USE_NEXTION_TFT_UPLOAD
|
||||
#define USE_NEXTION_WAVEFORM
|
||||
#define USE_NUMBER
|
||||
#define USE_OUTPUT
|
||||
#define USE_POWER_SUPPLY
|
||||
|
||||
@@ -214,8 +214,9 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
||||
#endif /* ESPHOME_DEBUG_SCHEDULER */
|
||||
}
|
||||
|
||||
// Common epilogue: atomic cancel-and-add (unless skip_cancel is true)
|
||||
if (!skip_cancel) {
|
||||
// Common epilogue: atomic cancel-and-add (unless skip_cancel is true or anonymous)
|
||||
// Anonymous items (STATIC_STRING with nullptr) can never match anything, so skip the scan.
|
||||
if (!skip_cancel && (name_type != NameType::STATIC_STRING || static_name != nullptr)) {
|
||||
this->cancel_item_locked_(component, name_type, static_name, hash_or_id, type, /* match_retry= */ false,
|
||||
/* find_first= */ true);
|
||||
}
|
||||
@@ -742,6 +743,23 @@ bool HOT Scheduler::cancel_item_(Component *component, NameType name_type, const
|
||||
// When find_first=false, cancels ALL matches across all containers (needed for
|
||||
// public cancel path where DelayAction parallel mode can create duplicates).
|
||||
// name_type determines matching: STATIC_STRING uses static_name, others use hash_or_id
|
||||
size_t Scheduler::mark_matching_items_removed_slow_locked_(std::vector<SchedulerItem *> &container,
|
||||
Component *component, NameType name_type,
|
||||
const char *static_name, uint32_t hash_or_id,
|
||||
SchedulerItem::Type type, bool match_retry,
|
||||
bool find_first) {
|
||||
size_t count = 0;
|
||||
for (auto *item : container) {
|
||||
if (this->matches_item_locked_(item, component, name_type, static_name, hash_or_id, type, match_retry)) {
|
||||
this->set_item_removed_(item, true);
|
||||
if (find_first)
|
||||
return 1;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
bool HOT Scheduler::cancel_item_locked_(Component *component, NameType name_type, const char *static_name,
|
||||
uint32_t hash_or_id, SchedulerItem::Type type, bool match_retry,
|
||||
bool find_first) {
|
||||
@@ -767,7 +785,7 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, NameType name_type
|
||||
// The main loop may be executing an item's callback right now, and recycling
|
||||
// would destroy the callback while it's running (use-after-free).
|
||||
// Only the main loop in call() should recycle items after execution completes.
|
||||
if (!this->items_.empty()) {
|
||||
{
|
||||
size_t heap_cancelled = this->mark_matching_items_removed_locked_(this->items_, component, name_type, static_name,
|
||||
hash_or_id, type, match_retry, find_first);
|
||||
total_cancelled += heap_cancelled;
|
||||
|
||||
+15
-15
@@ -495,23 +495,23 @@ class Scheduler {
|
||||
// name_type determines matching: STATIC_STRING uses static_name, others use hash_or_id
|
||||
// Returns the number of items marked for removal.
|
||||
// IMPORTANT: Must be called with scheduler lock held
|
||||
__attribute__((noinline)) size_t mark_matching_items_removed_locked_(std::vector<SchedulerItem *> &container,
|
||||
Component *component, NameType name_type,
|
||||
const char *static_name, uint32_t hash_or_id,
|
||||
SchedulerItem::Type type, bool match_retry,
|
||||
bool find_first = false) {
|
||||
size_t count = 0;
|
||||
for (auto *item : container) {
|
||||
if (this->matches_item_locked_(item, component, name_type, static_name, hash_or_id, type, match_retry)) {
|
||||
this->set_item_removed_(item, true);
|
||||
if (find_first)
|
||||
return 1;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
// Inlined: the fast path (empty container) avoids calling the out-of-line scan.
|
||||
inline size_t HOT mark_matching_items_removed_locked_(std::vector<SchedulerItem *> &container, Component *component,
|
||||
NameType name_type, const char *static_name,
|
||||
uint32_t hash_or_id, SchedulerItem::Type type, bool match_retry,
|
||||
bool find_first = false) {
|
||||
if (container.empty())
|
||||
return 0;
|
||||
return this->mark_matching_items_removed_slow_locked_(container, component, name_type, static_name, hash_or_id,
|
||||
type, match_retry, find_first);
|
||||
}
|
||||
|
||||
// Out-of-line slow path for mark_matching_items_removed_locked_ when container is non-empty.
|
||||
// IMPORTANT: Must be called with scheduler lock held
|
||||
__attribute__((noinline)) size_t mark_matching_items_removed_slow_locked_(
|
||||
std::vector<SchedulerItem *> &container, Component *component, NameType name_type, const char *static_name,
|
||||
uint32_t hash_or_id, SchedulerItem::Type type, bool match_retry, bool find_first);
|
||||
|
||||
Mutex lock_;
|
||||
std::vector<SchedulerItem *> items_;
|
||||
std::vector<SchedulerItem *> to_add_;
|
||||
|
||||
@@ -8,7 +8,24 @@ namespace esphome::benchmarks {
|
||||
// Inner iteration count to amortize CodSpeed instrumentation overhead.
|
||||
// Without this, the ~60ns per-iteration valgrind start/stop cost dominates
|
||||
// sub-microsecond benchmarks.
|
||||
static constexpr int kInnerIterations = 2000;
|
||||
// Must be divisible by all batch sizes used below (3, 10) to avoid
|
||||
// pool imbalance at iteration boundaries that causes spurious malloc.
|
||||
static constexpr int kInnerIterations = 2100;
|
||||
|
||||
// Warm the scheduler pool by registering and replacing items twice.
|
||||
// The first batch allocates fresh items; the second batch cancels them and
|
||||
// populates the recycling pool with the cancelled items from the first batch.
|
||||
static void warm_pool(Scheduler &scheduler, Component *component, int batch_size, uint32_t delay) {
|
||||
uint32_t now = millis();
|
||||
for (int i = 0; i < batch_size; i++) {
|
||||
scheduler.set_timeout(component, static_cast<uint32_t>(i), delay, []() {});
|
||||
}
|
||||
scheduler.call(++now);
|
||||
for (int i = 0; i < batch_size; i++) {
|
||||
scheduler.set_timeout(component, static_cast<uint32_t>(i), delay, []() {});
|
||||
}
|
||||
scheduler.call(++now);
|
||||
}
|
||||
|
||||
// --- Scheduler fast path: no work to do ---
|
||||
|
||||
@@ -83,11 +100,21 @@ static void Scheduler_SetTimeout(benchmark::State &state) {
|
||||
Scheduler scheduler;
|
||||
Component dummy_component;
|
||||
|
||||
// Register 3 timeouts then call() — realistic worst case where multiple
|
||||
// components schedule in the same loop iteration. Keeps item count within
|
||||
// the recycling pool (MAX_POOL_SIZE=5) to avoid spurious malloc/free.
|
||||
static constexpr int kBatchSize = 3;
|
||||
static_assert(kInnerIterations % kBatchSize == 0, "kInnerIterations must be divisible by kBatchSize");
|
||||
warm_pool(scheduler, &dummy_component, kBatchSize, 1000);
|
||||
for (auto _ : state) {
|
||||
uint32_t now = millis();
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
scheduler.set_timeout(&dummy_component, static_cast<uint32_t>(i % 5), 1000, []() {});
|
||||
scheduler.set_timeout(&dummy_component, static_cast<uint32_t>(i % kBatchSize), 1000, []() {});
|
||||
if ((i + 1) % kBatchSize == 0) {
|
||||
scheduler.call(++now);
|
||||
}
|
||||
}
|
||||
scheduler.process_to_add();
|
||||
scheduler.call(++now);
|
||||
benchmark::DoNotOptimize(scheduler);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
@@ -99,22 +126,22 @@ BENCHMARK(Scheduler_SetTimeout);
|
||||
static void Scheduler_SetInterval(benchmark::State &state) {
|
||||
Scheduler scheduler;
|
||||
Component dummy_component;
|
||||
// Number of distinct interval keys; controls how many unique timers exist
|
||||
// simultaneously and the drain cadence for process_to_add().
|
||||
static constexpr int kKeyCount = 5;
|
||||
|
||||
// Register 3 intervals then call() — realistic worst case where multiple
|
||||
// components schedule in the same loop iteration. Keeps item count within
|
||||
// the recycling pool (MAX_POOL_SIZE=5) to avoid spurious malloc/free.
|
||||
static constexpr int kBatchSize = 3;
|
||||
static_assert(kInnerIterations % kBatchSize == 0, "kInnerIterations must be divisible by kBatchSize");
|
||||
warm_pool(scheduler, &dummy_component, kBatchSize, 1000);
|
||||
for (auto _ : state) {
|
||||
uint32_t now = millis();
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
scheduler.set_interval(&dummy_component, static_cast<uint32_t>(i % kKeyCount), 1000, []() {});
|
||||
// Drain to_add_ periodically to reflect production behavior where
|
||||
// process_to_add() runs each main loop iteration. Without this,
|
||||
// cancelled items accumulate in to_add_ causing O(n²) scan cost.
|
||||
if ((i + 1) % kKeyCount == 0) {
|
||||
scheduler.process_to_add();
|
||||
scheduler.set_interval(&dummy_component, static_cast<uint32_t>(i % kBatchSize), 1000, []() {});
|
||||
if ((i + 1) % kBatchSize == 0) {
|
||||
scheduler.call(++now);
|
||||
}
|
||||
}
|
||||
// Final drain in case kInnerIterations is not a multiple of 5
|
||||
scheduler.process_to_add();
|
||||
scheduler.call(++now);
|
||||
benchmark::DoNotOptimize(scheduler);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
@@ -128,16 +155,79 @@ static void Scheduler_Defer(benchmark::State &state) {
|
||||
Component dummy_component;
|
||||
|
||||
// defer() is Component::defer which calls set_timeout(delay=0).
|
||||
// Call set_timeout directly since defer() is protected.
|
||||
// Component::defer(func) passes nullptr as the name, which skips
|
||||
// cancel_item_locked_ entirely — matching production behavior where
|
||||
// defers are anonymous fire-and-forget callbacks.
|
||||
static constexpr int kBatchSize = 3;
|
||||
static_assert(kInnerIterations % kBatchSize == 0, "kInnerIterations must be divisible by kBatchSize");
|
||||
warm_pool(scheduler, &dummy_component, kBatchSize, 0);
|
||||
for (auto _ : state) {
|
||||
uint32_t now = millis();
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
scheduler.set_timeout(&dummy_component, static_cast<uint32_t>(i % 5), 0, []() {});
|
||||
scheduler.set_timeout(&dummy_component, static_cast<const char *>(nullptr), 0, []() {});
|
||||
if ((i + 1) % kBatchSize == 0) {
|
||||
scheduler.call(++now);
|
||||
}
|
||||
}
|
||||
scheduler.process_to_add();
|
||||
scheduler.call(++now);
|
||||
benchmark::DoNotOptimize(scheduler);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(Scheduler_Defer);
|
||||
|
||||
// --- Scheduler: defer with same ID (cancel-and-replace pattern) ---
|
||||
|
||||
static void Scheduler_Defer_SameID(benchmark::State &state) {
|
||||
Scheduler scheduler;
|
||||
Component dummy_component;
|
||||
|
||||
// Measures defer with a fixed numeric ID — each call cancels the previous
|
||||
// pending defer before adding the new one. This is the pattern used by
|
||||
// components that defer work but want to coalesce rapid updates.
|
||||
static constexpr int kBatchSize = 3;
|
||||
static_assert(kInnerIterations % kBatchSize == 0, "kInnerIterations must be divisible by kBatchSize");
|
||||
warm_pool(scheduler, &dummy_component, kBatchSize, 0);
|
||||
for (auto _ : state) {
|
||||
uint32_t now = millis();
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
scheduler.set_timeout(&dummy_component, static_cast<uint32_t>(0), 0, []() {});
|
||||
if ((i + 1) % kBatchSize == 0) {
|
||||
scheduler.call(++now);
|
||||
}
|
||||
}
|
||||
scheduler.call(++now);
|
||||
benchmark::DoNotOptimize(scheduler);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(Scheduler_Defer_SameID);
|
||||
|
||||
// --- Scheduler: set_timeout with batch size exceeding pool (cliff test) ---
|
||||
|
||||
static void Scheduler_SetTimeout_ExceedPool(benchmark::State &state) {
|
||||
Scheduler scheduler;
|
||||
Component dummy_component;
|
||||
|
||||
// Register 10 timeouts then call() — exceeds MAX_POOL_SIZE=5 to measure
|
||||
// the performance cliff when the recycling pool is exhausted and items
|
||||
// must be malloc'd/freed.
|
||||
static constexpr int kBatchSize = 10;
|
||||
static_assert(kInnerIterations % kBatchSize == 0, "kInnerIterations must be divisible by kBatchSize");
|
||||
warm_pool(scheduler, &dummy_component, kBatchSize, 1000);
|
||||
for (auto _ : state) {
|
||||
uint32_t now = millis();
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
scheduler.set_timeout(&dummy_component, static_cast<uint32_t>(i % kBatchSize), 1000, []() {});
|
||||
if ((i + 1) % kBatchSize == 0) {
|
||||
scheduler.call(++now);
|
||||
}
|
||||
}
|
||||
scheduler.call(++now);
|
||||
benchmark::DoNotOptimize(scheduler);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(Scheduler_SetTimeout_ExceedPool);
|
||||
|
||||
} // namespace esphome::benchmarks
|
||||
|
||||
Reference in New Issue
Block a user