Merge upstream/dev, resolve conflict keeping LibreTiny sta_state_ guard

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
J. Nick Koston
2026-03-30 08:35:24 -10:00
co-authored by Claude Opus 4.6
40 changed files with 574 additions and 482 deletions
@@ -6,10 +6,7 @@
#ifdef USE_BSEC2
#include "bme68x_bsec2.h"
#include <string>
namespace esphome {
namespace bme68x_bsec2 {
namespace esphome::bme68x_bsec2 {
#define BME68X_BSEC2_ALGORITHM_OUTPUT_LOG(a) (a == ALGORITHM_OUTPUT_CLASSIFICATION ? "Classification" : "Regression")
#define BME68X_BSEC2_OPERATING_AGE_LOG(o) (o == OPERATING_AGE_4D ? "4 days" : "28 days")
@@ -18,9 +15,19 @@ namespace bme68x_bsec2 {
static const char *const TAG = "bme68x_bsec2.sensor";
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
static constexpr const char *const IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
static bool is_no_new_data_warning(int8_t status) {
#ifdef BME68X_W_NO_NEW_DATA
return status == BME68X_W_NO_NEW_DATA;
#else
return status == 2;
#endif
}
void BME68xBSEC2Component::setup() {
this->warn_if_blocking_over_ = 60; // initial reads may block for up to 60ms
this->bsec_status_ = bsec_init_m(&this->bsec_instance_);
if (this->bsec_status_ != BSEC_OK) {
this->mark_failed();
@@ -114,7 +121,8 @@ void BME68xBSEC2Component::loop() {
} else {
this->status_clear_error();
}
if (this->bsec_status_ > BSEC_OK || this->bme68x_status_ > BME68X_OK) {
const bool has_bme68x_warning = this->bme68x_status_ > BME68X_OK && !is_no_new_data_warning(this->bme68x_status_);
if (this->bsec_status_ > BSEC_OK || has_bme68x_warning) {
this->status_set_warning();
} else {
this->status_clear_warning();
@@ -130,7 +138,7 @@ void BME68xBSEC2Component::loop() {
void BME68xBSEC2Component::set_config_(const uint8_t *config, uint32_t len) {
if (len > BSEC_MAX_PROPERTY_BLOB_SIZE) {
ESP_LOGE(TAG, "Configuration is larger than BSEC_MAX_PROPERTY_BLOB_SIZE");
ESP_LOGE(TAG, "Configuration blob too large");
this->mark_failed();
return;
}
@@ -212,14 +220,12 @@ void BME68xBSEC2Component::run_() {
if (curr_time_ns < this->bsec_settings_.next_call) {
return;
}
uint8_t status;
ESP_LOGV(TAG, "Performing sensor run");
struct bme68x_conf bme68x_conf;
this->bsec_status_ = bsec_sensor_control_m(&this->bsec_instance_, curr_time_ns, &this->bsec_settings_);
if (this->bsec_status_ < BSEC_OK) {
ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC2 error code %d)", this->bsec_status_);
ESP_LOGW(TAG, "Fetching control settings failed (BSEC2 error code %d)", this->bsec_status_);
return;
}
@@ -235,9 +241,9 @@ void BME68xBSEC2Component::run_() {
this->bme68x_heatr_conf_.heatr_temp = this->bsec_settings_.heater_temperature;
this->bme68x_heatr_conf_.heatr_dur = this->bsec_settings_.heater_duration;
// status = bme68x_set_op_mode(this->bsec_settings_.op_mode, &this->bme68x_);
status = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
status = bme68x_set_op_mode(BME68X_FORCED_MODE, &this->bme68x_);
// this->bme68x_status_ = bme68x_set_op_mode(this->bsec_settings_.op_mode, &this->bme68x_);
this->bme68x_status_ = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
this->bme68x_status_ = bme68x_set_op_mode(BME68X_FORCED_MODE, &this->bme68x_);
this->op_mode_ = BME68X_FORCED_MODE;
ESP_LOGV(TAG, "Using forced mode");
@@ -259,9 +265,8 @@ void BME68xBSEC2Component::run_() {
BSEC_TOTAL_HEAT_DUR -
(bme68x_get_meas_dur(BME68X_PARALLEL_MODE, &bme68x_conf, &this->bme68x_) / INT64_C(1000));
status = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
status = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &this->bme68x_);
this->bme68x_status_ = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
this->bme68x_status_ = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &this->bme68x_);
this->op_mode_ = BME68X_PARALLEL_MODE;
ESP_LOGV(TAG, "Using parallel mode");
}
@@ -282,24 +287,15 @@ void BME68xBSEC2Component::run_() {
this->trigger_time_ns_ = curr_time_ns;
this->set_timeout("read", meas_dur / 1000, [this]() { this->read_(this->trigger_time_ns_); });
} else {
ESP_LOGV(TAG, "Measurement not required");
this->read_(curr_time_ns);
ESP_LOGV(TAG, "Measurement not required, queueing immediate read");
this->trigger_time_ns_ = curr_time_ns;
this->set_timeout("read", 0, [this]() { this->read_(this->trigger_time_ns_); });
}
}
void BME68xBSEC2Component::read_(int64_t trigger_time_ns) {
ESP_LOGV(TAG, "Reading data");
if (this->bsec_settings_.trigger_measurement) {
uint8_t current_op_mode;
this->bme68x_status_ = bme68x_get_op_mode(&current_op_mode, &this->bme68x_);
if (current_op_mode == BME68X_SLEEP_MODE) {
ESP_LOGV(TAG, "Still in sleep mode, doing nothing");
return;
}
}
if (!this->bsec_settings_.process_data) {
ESP_LOGV(TAG, "Data processing not required");
return;
@@ -309,12 +305,16 @@ void BME68xBSEC2Component::read_(int64_t trigger_time_ns) {
uint8_t nFields = 0;
this->bme68x_status_ = bme68x_get_data(this->op_mode_, &data[0], &nFields, &this->bme68x_);
if (is_no_new_data_warning(this->bme68x_status_)) {
ESP_LOGV(TAG, "BME68X did not provide new data");
return;
}
if (this->bme68x_status_ != BME68X_OK) {
ESP_LOGW(TAG, "Failed to get sensor data (BME68X error code %d)", this->bme68x_status_);
ESP_LOGW(TAG, "Fetching data failed (BME68X error code %d)", this->bme68x_status_);
return;
}
if (nFields < 1) {
ESP_LOGD(TAG, "BME68X did not provide new data");
ESP_LOGV(TAG, "BME68X did not provide new fields");
return;
}
@@ -373,7 +373,7 @@ void BME68xBSEC2Component::read_(int64_t trigger_time_ns) {
uint8_t num_outputs = BSEC_NUMBER_OUTPUTS;
this->bsec_status_ = bsec_do_steps_m(&this->bsec_instance_, inputs, num_inputs, outputs, &num_outputs);
if (this->bsec_status_ != BSEC_OK) {
ESP_LOGW(TAG, "BSEC2 failed to process signals (BSEC2 error code %d)", this->bsec_status_);
ESP_LOGW(TAG, "Signal processing failed (BSEC2 error code %d)", this->bsec_status_);
return;
}
if (num_outputs < 1) {
@@ -474,7 +474,7 @@ void BME68xBSEC2Component::publish_sensor_(sensor::Sensor *sensor, float value,
#endif
#ifdef USE_TEXT_SENSOR
void BME68xBSEC2Component::publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value) {
void BME68xBSEC2Component::publish_sensor_(text_sensor::TextSensor *sensor, const char *value) {
if (!sensor || (sensor->has_state() && sensor->state == value)) {
return;
}
@@ -526,6 +526,5 @@ void BME68xBSEC2Component::save_state_(uint8_t accuracy) {
ESP_LOGI(TAG, "Saved state");
}
} // namespace bme68x_bsec2
} // namespace esphome
} // namespace esphome::bme68x_bsec2
#endif
+29 -33
View File
@@ -19,8 +19,7 @@
#include <bsec2.h>
namespace esphome {
namespace bme68x_bsec2 {
namespace esphome::bme68x_bsec2 {
enum AlgorithmOutput {
ALGORITHM_OUTPUT_IAQ,
@@ -97,7 +96,7 @@ class BME68xBSEC2Component : public Component {
void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only = false);
#endif
#ifdef USE_TEXT_SENSOR
void publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value);
void publish_sensor_(text_sensor::TextSensor *sensor, const char *value);
#endif
void load_state_();
@@ -108,39 +107,12 @@ class BME68xBSEC2Component : public Component {
struct bme68x_dev bme68x_;
bsec_bme_settings_t bsec_settings_;
bsec_version_t version_;
uint8_t bsec_instance_[BSEC_INSTANCE_SIZE];
struct bme68x_heatr_conf bme68x_heatr_conf_;
uint8_t op_mode_; // operating mode of sensor
bsec_library_return_t bsec_status_{BSEC_OK};
int8_t bme68x_status_{BME68X_OK};
int64_t last_time_ms_{0};
int64_t trigger_time_ns_{0}; // Stored for set_timeout lambda to help avoid heap allocation on supported 32-bit
// toolchains with small std::function SBO
uint32_t millis_overflow_counter_{0};
std::queue<std::function<void()>> queue_;
ESPPreferenceObject bsec_state_;
uint8_t const *bsec2_configuration_{nullptr};
uint32_t bsec2_configuration_length_{0};
bool bsec2_blob_configured_{false};
ESPPreferenceObject bsec_state_;
uint32_t state_save_interval_ms_{21600000}; // 6 hours - 4 times a day
uint32_t last_state_save_ms_ = 0;
float temperature_offset_{0};
AlgorithmOutput algorithm_output_{ALGORITHM_OUTPUT_IAQ};
OperatingAge operating_age_{OPERATING_AGE_28D};
Voltage voltage_{VOLTAGE_3_3V};
SampleRate sample_rate_{SAMPLE_RATE_LP}; // Core/gas sample rate
SampleRate temperature_sample_rate_{SAMPLE_RATE_DEFAULT};
SampleRate pressure_sample_rate_{SAMPLE_RATE_DEFAULT};
SampleRate humidity_sample_rate_{SAMPLE_RATE_DEFAULT};
#ifdef USE_SENSOR
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *pressure_sensor_{nullptr};
@@ -155,8 +127,32 @@ class BME68xBSEC2Component : public Component {
#ifdef USE_TEXT_SENSOR
text_sensor::TextSensor *iaq_accuracy_text_sensor_{nullptr};
#endif
int64_t last_time_ms_{0};
int64_t trigger_time_ns_{0}; // Stored for set_timeout lambda to help avoid heap allocation on supported 32-bit
// toolchains with small std::function SBO
uint32_t state_save_interval_ms_{21600000}; // 6 hours - 4 times a day
uint32_t last_state_save_ms_{0};
uint32_t millis_overflow_counter_{0};
uint32_t bsec2_configuration_length_{0};
bsec_library_return_t bsec_status_{BSEC_OK};
float temperature_offset_{0};
AlgorithmOutput algorithm_output_{ALGORITHM_OUTPUT_IAQ};
OperatingAge operating_age_{OPERATING_AGE_28D};
Voltage voltage_{VOLTAGE_3_3V};
SampleRate sample_rate_{SAMPLE_RATE_LP}; // Core/gas sample rate
SampleRate temperature_sample_rate_{SAMPLE_RATE_DEFAULT};
SampleRate pressure_sample_rate_{SAMPLE_RATE_DEFAULT};
SampleRate humidity_sample_rate_{SAMPLE_RATE_DEFAULT};
uint8_t bsec_instance_[BSEC_INSTANCE_SIZE];
uint8_t op_mode_; // operating mode of sensor
int8_t bme68x_status_{BME68X_OK};
bool bsec2_blob_configured_{false};
};
} // namespace bme68x_bsec2
} // namespace esphome
} // namespace esphome::bme68x_bsec2
#endif
+11 -17
View File
@@ -2,8 +2,7 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace dht {
namespace esphome::dht {
static const char *const TAG = "dht";
@@ -45,16 +44,13 @@ void DHT::update() {
}
if (success) {
ESP_LOGD(TAG, "Temperature %.1f°C Humidity %.1f%%", temperature, humidity);
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature);
if (this->humidity_sensor_ != nullptr)
this->humidity_sensor_->publish_state(humidity);
this->status_clear_warning();
} else {
ESP_LOGW(TAG, "Invalid readings! Check pin number and pull-up resistor%s.",
this->is_auto_detect_ ? " and try manually specifying the model" : "");
ESP_LOGW(TAG, "Invalid readings");
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(NAN);
if (this->humidity_sensor_ != nullptr)
@@ -73,8 +69,7 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
*temperature = NAN;
int error_code = 0;
int8_t i = 0;
uint8_t data[5] = {0, 0, 0, 0, 0};
uint8_t data[5] = {};
#ifndef USE_ESP32
this->pin_.pin_mode(gpio::FLAG_OUTPUT);
@@ -107,7 +102,9 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
uint8_t bit = 7;
uint8_t byte = 0;
for (i = -1; i < 40; i++) {
// On 32-bit Xtensa/RISC-V cores, int8_t would require masking/sign-extension for comparisons
// vs. native int. Using int i is native word size — small win in the timing-critical section.
for (int i = -1; i < 40; i++) {
uint32_t start_time = micros();
// Wait for rising edge
@@ -156,11 +153,9 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
}
}
}
if (!report_errors && error_code != 0)
return false;
if (error_code) {
ESP_LOGW(TAG, ESP_LOG_MSG_COMM_FAIL);
if (error_code != 0) {
if (report_errors)
ESP_LOGW(TAG, ESP_LOG_MSG_COMM_FAIL);
return false;
}
@@ -177,7 +172,7 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
if (checksum_a != data[4] && checksum_b != data[4]) {
if (report_errors) {
ESP_LOGW(TAG, "Checksum invalid: %u!=%u", checksum_a, data[4]);
ESP_LOGW(TAG, "Invalid checksum");
}
return false;
}
@@ -234,5 +229,4 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
return true;
}
} // namespace dht
} // namespace esphome
} // namespace esphome::dht
+5 -8
View File
@@ -4,10 +4,9 @@
#include "esphome/core/hal.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace dht {
namespace esphome::dht {
enum DHTModel {
enum DHTModel : uint8_t {
DHT_MODEL_AUTO_DETECT = 0,
DHT_MODEL_DHT11,
DHT_MODEL_DHT22,
@@ -42,7 +41,6 @@ class DHT : public PollingComponent {
this->t_pin_ = pin;
this->pin_ = pin->to_isr();
}
void set_model(DHTModel model) { model_ = model; }
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
@@ -55,13 +53,12 @@ class DHT : public PollingComponent {
protected:
bool read_sensor_(float *temperature, float *humidity, bool report_errors);
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
InternalGPIOPin *t_pin_;
ISRInternalGPIOPin pin_;
DHTModel model_{DHT_MODEL_AUTO_DETECT};
bool is_auto_detect_{false};
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
};
} // namespace dht
} // namespace esphome
} // namespace esphome::dht
+57 -9
View File
@@ -134,10 +134,38 @@ class HandlerCounts:
_handler_counts = HandlerCounts()
def _add_callback(
parent_var: cg.MockObj,
method: str,
handler_var: cg.MockObj,
params: str,
call_args: str,
) -> None:
"""Generate a lambda callback that forwards to a handler method.
Uses a braced scope with a local pointer variable so the generated C++
lambda captures only that pointer, avoiding GCC warnings about capturing
variables with static storage duration.
"""
cg.add(
cg.RawStatement(
f"{{ auto *h = {handler_var}; "
f"{parent_var}->{method}("
f"[h]({params}) {{ h->{call_args}; }}); }}"
)
)
def register_gap_event_handler(parent_var: cg.MockObj, handler_var: cg.MockObj) -> None:
"""Register a GAP event handler and track the count."""
_handler_counts.gap_event += 1
cg.add(parent_var.register_gap_event_handler(handler_var))
_add_callback(
parent_var,
"add_gap_event_callback",
handler_var,
"esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param",
"gap_event_handler(event, param)",
)
def register_gap_scan_event_handler(
@@ -145,7 +173,13 @@ def register_gap_scan_event_handler(
) -> None:
"""Register a GAP scan event handler and track the count."""
_handler_counts.gap_scan_event += 1
cg.add(parent_var.register_gap_scan_event_handler(handler_var))
_add_callback(
parent_var,
"add_gap_scan_event_callback",
handler_var,
"const esphome::esp32_ble::BLEScanResult &scan_result",
"gap_scan_event_handler(scan_result)",
)
def register_gattc_event_handler(
@@ -153,7 +187,13 @@ def register_gattc_event_handler(
) -> None:
"""Register a GATTc event handler and track the count."""
_handler_counts.gattc_event += 1
cg.add(parent_var.register_gattc_event_handler(handler_var))
_add_callback(
parent_var,
"add_gattc_event_callback",
handler_var,
"esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param",
"gattc_event_handler(event, gattc_if, param)",
)
def register_gatts_event_handler(
@@ -161,7 +201,13 @@ def register_gatts_event_handler(
) -> None:
"""Register a GATTs event handler and track the count."""
_handler_counts.gatts_event += 1
cg.add(parent_var.register_gatts_event_handler(handler_var))
_add_callback(
parent_var,
"add_gatts_event_callback",
handler_var,
"esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param",
"gatts_event_handler(event, gatts_if, param)",
)
def register_ble_status_event_handler(
@@ -169,7 +215,13 @@ def register_ble_status_event_handler(
) -> None:
"""Register a BLE status event handler and track the count."""
_handler_counts.ble_status_event += 1
cg.add(parent_var.register_ble_status_event_handler(handler_var))
_add_callback(
parent_var,
"add_ble_status_event_callback",
handler_var,
"",
"ble_before_disabled_event_handler()",
)
def register_bt_logger(*loggers: BTLoggers) -> None:
@@ -225,10 +277,6 @@ NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2]
esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble")
ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component)
GAPEventHandler = esp32_ble_ns.class_("GAPEventHandler")
GATTcEventHandler = esp32_ble_ns.class_("GATTcEventHandler")
GATTsEventHandler = esp32_ble_ns.class_("GATTsEventHandler")
BLEEnabledCondition = esp32_ble_ns.class_("BLEEnabledCondition", automation.Condition)
BLEEnableAction = esp32_ble_ns.class_("BLEEnableAction", automation.Action)
BLEDisableAction = esp32_ble_ns.class_("BLEDisableAction", automation.Action)
+5 -16
View File
@@ -408,9 +408,7 @@ void ESP32BLE::loop() {
esp_gatt_if_t gatts_if = ble_event->event_.gatts.gatts_if;
esp_ble_gatts_cb_param_t *param = &ble_event->event_.gatts.gatts_param;
ESP_LOGV(TAG, "gatts_event [esp_gatt_if: %d] - %d", gatts_if, event);
for (auto *gatts_handler : this->gatts_event_handlers_) {
gatts_handler->gatts_event_handler(event, gatts_if, param);
}
this->gatts_event_callbacks_.call(event, gatts_if, param);
break;
}
#endif
@@ -420,9 +418,7 @@ void ESP32BLE::loop() {
esp_gatt_if_t gattc_if = ble_event->event_.gattc.gattc_if;
esp_ble_gattc_cb_param_t *param = &ble_event->event_.gattc.gattc_param;
ESP_LOGV(TAG, "gattc_event [esp_gatt_if: %d] - %d", gattc_if, event);
for (auto *gattc_handler : this->gattc_event_handlers_) {
gattc_handler->gattc_event_handler(event, gattc_if, param);
}
this->gattc_event_callbacks_.call(event, gattc_if, param);
break;
}
#endif
@@ -431,10 +427,7 @@ void ESP32BLE::loop() {
switch (gap_event) {
case ESP_GAP_BLE_SCAN_RESULT_EVT:
#ifdef ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT
// Use the new scan event handler - no memcpy!
for (auto *scan_handler : this->gap_scan_event_handlers_) {
scan_handler->gap_scan_event_handler(ble_event->scan_result());
}
this->gap_scan_event_callbacks_.call(ble_event->scan_result());
#endif
break;
@@ -478,9 +471,7 @@ void ESP32BLE::loop() {
}
// clang-format on
// Dispatch to all registered handlers
for (auto *gap_handler : this->gap_event_handlers_) {
gap_handler->gap_event_handler(gap_event, param);
}
this->gap_event_callbacks_.call(gap_event, param);
}
#endif
break;
@@ -518,9 +509,7 @@ void ESP32BLE::loop_handle_state_transition_not_active_() {
ESP_LOGD(TAG, "Disabling");
#ifdef ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT
for (auto *ble_event_handler : this->ble_status_event_handlers_) {
ble_event_handler->ble_before_disabled_event_handler();
}
this->ble_status_event_callbacks_.call();
#endif
if (!ble_dismantle_()) {
+25 -44
View File
@@ -87,37 +87,6 @@ enum BLEComponentState : uint8_t {
BLE_COMPONENT_STATE_ACTIVE,
};
class GAPEventHandler {
public:
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
};
class GAPScanEventHandler {
public:
virtual void gap_scan_event_handler(const BLEScanResult &scan_result) = 0;
};
#ifdef USE_ESP32_BLE_CLIENT
class GATTcEventHandler {
public:
virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) = 0;
};
#endif
#ifdef USE_ESP32_BLE_SERVER
class GATTsEventHandler {
public:
virtual void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param) = 0;
};
#endif
class BLEStatusEventHandler {
public:
virtual void ble_before_disabled_event_handler() = 0;
};
class ESP32BLE : public Component {
public:
void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; }
@@ -154,22 +123,28 @@ class ESP32BLE : public Component {
#endif
#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT
void register_gap_event_handler(GAPEventHandler *handler) { this->gap_event_handlers_.push_back(handler); }
template<typename F> void add_gap_event_callback(F &&callback) {
this->gap_event_callbacks_.add(std::forward<F>(callback));
}
#endif
#ifdef ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT
void register_gap_scan_event_handler(GAPScanEventHandler *handler) {
this->gap_scan_event_handlers_.push_back(handler);
template<typename F> void add_gap_scan_event_callback(F &&callback) {
this->gap_scan_event_callbacks_.add(std::forward<F>(callback));
}
#endif
#if defined(USE_ESP32_BLE_CLIENT) && defined(ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT)
void register_gattc_event_handler(GATTcEventHandler *handler) { this->gattc_event_handlers_.push_back(handler); }
template<typename F> void add_gattc_event_callback(F &&callback) {
this->gattc_event_callbacks_.add(std::forward<F>(callback));
}
#endif
#if defined(USE_ESP32_BLE_SERVER) && defined(ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT)
void register_gatts_event_handler(GATTsEventHandler *handler) { this->gatts_event_handlers_.push_back(handler); }
template<typename F> void add_gatts_event_callback(F &&callback) {
this->gatts_event_callbacks_.add(std::forward<F>(callback));
}
#endif
#ifdef ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT
void register_ble_status_event_handler(BLEStatusEventHandler *handler) {
this->ble_status_event_handlers_.push_back(handler);
template<typename F> void add_ble_status_event_callback(F &&callback) {
this->ble_status_event_callbacks_.add(std::forward<F>(callback));
}
#endif
void set_enable_on_boot(bool enable_on_boot) { this->enable_on_boot_ = enable_on_boot; }
@@ -202,21 +177,27 @@ class ESP32BLE : public Component {
private:
template<typename... Args> friend void enqueue_ble_event(Args... args);
// Handler vectors - use StaticVector when counts are known at compile time
#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT
StaticVector<GAPEventHandler *, ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT> gap_event_handlers_;
StaticCallbackManager<ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT,
void(esp_gap_ble_cb_event_t, esp_ble_gap_cb_param_t *)>
gap_event_callbacks_;
#endif
#ifdef ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT
StaticVector<GAPScanEventHandler *, ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT> gap_scan_event_handlers_;
StaticCallbackManager<ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT, void(const BLEScanResult &)>
gap_scan_event_callbacks_;
#endif
#if defined(USE_ESP32_BLE_CLIENT) && defined(ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT)
StaticVector<GATTcEventHandler *, ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT> gattc_event_handlers_;
StaticCallbackManager<ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT,
void(esp_gattc_cb_event_t, esp_gatt_if_t, esp_ble_gattc_cb_param_t *)>
gattc_event_callbacks_;
#endif
#if defined(USE_ESP32_BLE_SERVER) && defined(ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT)
StaticVector<GATTsEventHandler *, ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT> gatts_event_handlers_;
StaticCallbackManager<ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT,
void(esp_gatts_cb_event_t, esp_gatt_if_t, esp_ble_gatts_cb_param_t *)>
gatts_event_callbacks_;
#endif
#ifdef ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT
StaticVector<BLEStatusEventHandler *, ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT> ble_status_event_handlers_;
StaticCallbackManager<ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT, void()> ble_status_event_callbacks_;
#endif
// Large objects (size depends on template parameters, but typically aligned to 4 bytes)
@@ -13,7 +13,6 @@ esp32_ble_beacon_ns = cg.esphome_ns.namespace("esp32_ble_beacon")
ESP32BLEBeacon = esp32_ble_beacon_ns.class_(
"ESP32BLEBeacon",
cg.Component,
esp32_ble.GAPEventHandler,
cg.Parented.template(esp32_ble.ESP32BLE),
)
CONF_MAJOR = "major"
@@ -35,7 +35,7 @@ using esp_ble_ibeacon_t = struct {
using namespace esp32_ble;
class ESP32BLEBeacon : public Component, public GAPEventHandler, public Parented<ESP32BLE> {
class ESP32BLEBeacon : public Component, public Parented<ESP32BLE> {
public:
explicit ESP32BLEBeacon(const std::array<uint8_t, 16> &uuid) : uuid_(uuid) {}
@@ -51,7 +51,7 @@ class ESP32BLEBeacon : public Component, public GAPEventHandler, public Parented
#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID
void set_tx_power(esp_power_level_t val) { this->tx_power_ = val; }
#endif
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
protected:
void on_advertise_();
@@ -72,7 +72,6 @@ BLECharacteristic_ns = esp32_ble_server_ns.namespace("BLECharacteristic")
BLEServer = esp32_ble_server_ns.class_(
"BLEServer",
cg.Component,
esp32_ble.GATTsEventHandler,
cg.Parented.template(esp32_ble.ESP32BLE),
)
esp32_ble_server_automations_ns = esp32_ble_server_ns.namespace(
@@ -24,7 +24,7 @@ namespace esp32_ble_server {
using namespace esp32_ble;
using namespace bytebuffer;
class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEventHandler, public Parented<ESP32BLE> {
class BLEServer : public Component, public Parented<ESP32BLE> {
public:
void setup() override;
void loop() override;
@@ -53,10 +53,9 @@ class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEv
const uint16_t *get_clients() const { return this->clients_; }
uint8_t get_client_count() const { return this->client_count_; }
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param) override;
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
void ble_before_disabled_event_handler() override;
void ble_before_disabled_event_handler();
// Direct callback registration - supports multiple callbacks
void on_connect(std::function<void(uint16_t)> &&callback) {
@@ -90,8 +90,6 @@ esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker")
ESP32BLETracker = esp32_ble_tracker_ns.class_(
"ESP32BLETracker",
cg.Component,
esp32_ble.GAPEventHandler,
esp32_ble.GATTcEventHandler,
cg.Parented.template(esp32_ble.ESP32BLE),
)
ESPBTClient = esp32_ble_tracker_ns.class_("ESPBTClient")
@@ -291,10 +291,6 @@ class ESPBTClient : public ESPBTDeviceListener {
};
class ESP32BLETracker : public Component,
public GAPEventHandler,
public GAPScanEventHandler,
public GATTcEventHandler,
public BLEStatusEventHandler,
#ifdef USE_OTA_STATE_LISTENER
public ota::OTAGlobalStateListener,
#endif
@@ -325,11 +321,10 @@ class ESP32BLETracker : public Component,
void start_scan();
void stop_scan();
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
void gap_scan_event_handler(const BLEScanResult &scan_result) override;
void ble_before_disabled_event_handler() override;
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
void gap_scan_event_handler(const BLEScanResult &scan_result);
void ble_before_disabled_event_handler();
#ifdef USE_OTA_STATE_LISTENER
void on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) override;
+83
View File
@@ -0,0 +1,83 @@
import esphome.codegen as cg
modbus_ns = cg.esphome_ns.namespace("modbus")
modbus_helpers_ns = modbus_ns.namespace("helpers")
ModbusFunctionCode_ns = modbus_ns.namespace("ModbusFunctionCode")
ModbusFunctionCode = ModbusFunctionCode_ns.enum("ModbusFunctionCode")
MODBUS_FUNCTION_CODE = {
"read_coils": ModbusFunctionCode.READ_COILS,
"read_discrete_inputs": ModbusFunctionCode.READ_DISCRETE_INPUTS,
"read_holding_registers": ModbusFunctionCode.READ_HOLDING_REGISTERS,
"read_input_registers": ModbusFunctionCode.READ_INPUT_REGISTERS,
"write_single_coil": ModbusFunctionCode.WRITE_SINGLE_COIL,
"write_single_register": ModbusFunctionCode.WRITE_SINGLE_REGISTER,
"write_multiple_coils": ModbusFunctionCode.WRITE_MULTIPLE_COILS,
"write_multiple_registers": ModbusFunctionCode.WRITE_MULTIPLE_REGISTERS,
}
ModbusRegisterType_ns = modbus_ns.namespace("ModbusRegisterType")
ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType")
MODBUS_WRITE_REGISTER_TYPE = {
"custom": ModbusRegisterType.CUSTOM,
"coil": ModbusRegisterType.COIL,
"holding": ModbusRegisterType.HOLDING,
}
MODBUS_REGISTER_TYPE = {
**MODBUS_WRITE_REGISTER_TYPE,
"discrete_input": ModbusRegisterType.DISCRETE_INPUT,
"read": ModbusRegisterType.READ,
}
SensorValueType_ns = modbus_helpers_ns.namespace("SensorValueType")
SensorValueType = SensorValueType_ns.enum("SensorValueType")
SENSOR_VALUE_TYPE = {
"RAW": SensorValueType.RAW,
"U_WORD": SensorValueType.U_WORD,
"S_WORD": SensorValueType.S_WORD,
"U_DWORD": SensorValueType.U_DWORD,
"U_DWORD_R": SensorValueType.U_DWORD_R,
"S_DWORD": SensorValueType.S_DWORD,
"S_DWORD_R": SensorValueType.S_DWORD_R,
"U_QWORD": SensorValueType.U_QWORD,
"U_QWORD_R": SensorValueType.U_QWORD_R,
"S_QWORD": SensorValueType.S_QWORD,
"S_QWORD_R": SensorValueType.S_QWORD_R,
"FP32": SensorValueType.FP32,
"FP32_R": SensorValueType.FP32_R,
}
TYPE_REGISTER_MAP = {
"RAW": 1,
"U_WORD": 1,
"S_WORD": 1,
"U_DWORD": 2,
"U_DWORD_R": 2,
"S_DWORD": 2,
"S_DWORD_R": 2,
"U_QWORD": 4,
"U_QWORD_R": 4,
"S_QWORD": 4,
"S_QWORD_R": 4,
"FP32": 2,
"FP32_R": 2,
}
CPP_TYPE_REGISTER_MAP = {
"RAW": cg.uint16,
"U_WORD": cg.uint16,
"S_WORD": cg.int16,
"U_DWORD": cg.uint32,
"U_DWORD_R": cg.uint32,
"S_DWORD": cg.int32,
"S_DWORD_R": cg.int32,
"U_QWORD": cg.uint64,
"U_QWORD_R": cg.uint64,
"S_QWORD": cg.int64,
"S_QWORD_R": cg.int64,
"FP32": cg.float_,
"FP32_R": cg.float_,
}
+106
View File
@@ -0,0 +1,106 @@
#pragma once
#include <string>
#include "esphome/core/helpers.h"
#include "esphome/components/modbus/modbus_definitions.h"
namespace esphome::modbus::helpers {
enum class SensorValueType : uint8_t {
RAW = 0x00, // variable length
U_WORD = 0x1, // 1 Register unsigned
U_DWORD = 0x2, // 2 Registers unsigned
S_WORD = 0x3, // 1 Register signed
S_DWORD = 0x4, // 2 Registers signed
BIT = 0x5,
U_DWORD_R = 0x6, // 2 Registers unsigned
S_DWORD_R = 0x7, // 2 Registers unsigned
U_QWORD = 0x8,
S_QWORD = 0x9,
U_QWORD_R = 0xA,
S_QWORD_R = 0xB,
FP32 = 0xC,
FP32_R = 0xD
};
inline bool value_type_is_float(SensorValueType v) {
return v == SensorValueType::FP32 || v == SensorValueType::FP32_R;
}
inline ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type) {
switch (reg_type) {
case ModbusRegisterType::COIL:
return ModbusFunctionCode::READ_COILS;
case ModbusRegisterType::DISCRETE_INPUT:
return ModbusFunctionCode::READ_DISCRETE_INPUTS;
case ModbusRegisterType::HOLDING:
return ModbusFunctionCode::READ_HOLDING_REGISTERS;
case ModbusRegisterType::READ:
return ModbusFunctionCode::READ_INPUT_REGISTERS;
default:
return ModbusFunctionCode::CUSTOM;
}
}
inline ModbusFunctionCode modbus_register_write_function(ModbusRegisterType reg_type) {
switch (reg_type) {
case ModbusRegisterType::COIL:
return ModbusFunctionCode::WRITE_SINGLE_COIL;
case ModbusRegisterType::DISCRETE_INPUT:
return ModbusFunctionCode::CUSTOM;
case ModbusRegisterType::HOLDING:
return ModbusFunctionCode::READ_WRITE_MULTIPLE_REGISTERS;
case ModbusRegisterType::READ:
default:
return ModbusFunctionCode::CUSTOM;
}
}
inline uint8_t c_to_hex(char c) { return (c >= 'A') ? (c >= 'a') ? (c - 'a' + 10) : (c - 'A' + 10) : (c - '0'); }
/** Get a byte from a hex string
* byte_from_hex_str("1122", 1) returns uint_8 value 0x22 == 34
* byte_from_hex_str("1122", 0) returns 0x11
* @param value string containing hex encoding
* @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in
* the hex string is byte_pos * 2
* @return byte value
*/
inline uint8_t byte_from_hex_str(const std::string &value, uint8_t pos) {
if (value.length() < pos * 2 + 2)
return 0;
return (c_to_hex(value[pos * 2]) << 4) | c_to_hex(value[pos * 2 + 1]);
}
/** Get a word from a hex string
* @param value string containing hex encoding
* @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in
* the hex string is byte_pos * 2
* @return word value
*/
inline uint16_t word_from_hex_str(const std::string &value, uint8_t pos) {
return byte_from_hex_str(value, pos) << 8 | byte_from_hex_str(value, pos + 1);
}
/** Get a dword from a hex string
* @param value string containing hex encoding
* @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in
* the hex string is byte_pos * 2
* @return dword value
*/
inline uint32_t dword_from_hex_str(const std::string &value, uint8_t pos) {
return word_from_hex_str(value, pos) << 16 | word_from_hex_str(value, pos + 2);
}
/** Get a qword from a hex string
* @param value string containing hex encoding
* @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in
* the hex string is byte_pos * 2
* @return qword value
*/
inline uint64_t qword_from_hex_str(const std::string &value, uint8_t pos) {
return static_cast<uint64_t>(dword_from_hex_str(value, pos)) << 32 | dword_from_hex_str(value, pos + 4);
}
} // namespace esphome::modbus::helpers
@@ -4,6 +4,13 @@ from esphome import automation
import esphome.codegen as cg
from esphome.components import modbus
from esphome.components.const import CONF_ENABLED
from esphome.components.modbus.helpers import (
CPP_TYPE_REGISTER_MAP,
MODBUS_REGISTER_TYPE,
SENSOR_VALUE_TYPE,
TYPE_REGISTER_MAP,
ModbusRegisterType,
)
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_NAME, CONF_OFFSET
from esphome.cpp_helpers import logging
@@ -41,7 +48,6 @@ CONF_SERVER_REGISTERS = "server_registers"
MULTI_CONF = True
modbus_controller_ns = cg.esphome_ns.namespace("modbus_controller")
modbus_ns = cg.esphome_ns.namespace("modbus")
ModbusController = modbus_controller_ns.class_(
"ModbusController", cg.PollingComponent, modbus.ModbusDevice
)
@@ -50,85 +56,6 @@ SensorItem = modbus_controller_ns.struct("SensorItem")
ServerCourtesyResponse = modbus_controller_ns.struct("ServerCourtesyResponse")
ServerRegister = modbus_controller_ns.struct("ServerRegister")
ModbusFunctionCode_ns = modbus_ns.namespace("ModbusFunctionCode")
ModbusFunctionCode = ModbusFunctionCode_ns.enum("ModbusFunctionCode")
MODBUS_FUNCTION_CODE = {
"read_coils": ModbusFunctionCode.READ_COILS,
"read_discrete_inputs": ModbusFunctionCode.READ_DISCRETE_INPUTS,
"read_holding_registers": ModbusFunctionCode.READ_HOLDING_REGISTERS,
"read_input_registers": ModbusFunctionCode.READ_INPUT_REGISTERS,
"write_single_coil": ModbusFunctionCode.WRITE_SINGLE_COIL,
"write_single_register": ModbusFunctionCode.WRITE_SINGLE_REGISTER,
"write_multiple_coils": ModbusFunctionCode.WRITE_MULTIPLE_COILS,
"write_multiple_registers": ModbusFunctionCode.WRITE_MULTIPLE_REGISTERS,
}
ModbusRegisterType_ns = modbus_controller_ns.namespace("ModbusRegisterType")
ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType")
MODBUS_WRITE_REGISTER_TYPE = {
"custom": ModbusRegisterType.CUSTOM,
"coil": ModbusRegisterType.COIL,
"holding": ModbusRegisterType.HOLDING,
}
MODBUS_REGISTER_TYPE = {
**MODBUS_WRITE_REGISTER_TYPE,
"discrete_input": ModbusRegisterType.DISCRETE_INPUT,
"read": ModbusRegisterType.READ,
}
SensorValueType_ns = modbus_controller_ns.namespace("SensorValueType")
SensorValueType = SensorValueType_ns.enum("SensorValueType")
SENSOR_VALUE_TYPE = {
"RAW": SensorValueType.RAW,
"U_WORD": SensorValueType.U_WORD,
"S_WORD": SensorValueType.S_WORD,
"U_DWORD": SensorValueType.U_DWORD,
"U_DWORD_R": SensorValueType.U_DWORD_R,
"S_DWORD": SensorValueType.S_DWORD,
"S_DWORD_R": SensorValueType.S_DWORD_R,
"U_QWORD": SensorValueType.U_QWORD,
"U_QWORD_R": SensorValueType.U_QWORD_R,
"S_QWORD": SensorValueType.S_QWORD,
"S_QWORD_R": SensorValueType.S_QWORD_R,
"FP32": SensorValueType.FP32,
"FP32_R": SensorValueType.FP32_R,
}
TYPE_REGISTER_MAP = {
"RAW": 1,
"U_WORD": 1,
"S_WORD": 1,
"U_DWORD": 2,
"U_DWORD_R": 2,
"S_DWORD": 2,
"S_DWORD_R": 2,
"U_QWORD": 4,
"U_QWORD_R": 4,
"S_QWORD": 4,
"S_QWORD_R": 4,
"FP32": 2,
"FP32_R": 2,
}
CPP_TYPE_REGISTER_MAP = {
"RAW": cg.uint16,
"U_WORD": cg.uint16,
"S_WORD": cg.int16,
"U_DWORD": cg.uint32,
"U_DWORD_R": cg.uint32,
"S_DWORD": cg.int32,
"S_DWORD_R": cg.int32,
"U_QWORD": cg.uint64,
"U_QWORD_R": cg.uint64,
"S_QWORD": cg.int64,
"S_QWORD_R": cg.int64,
"FP32": cg.float_,
"FP32_R": cg.float_,
}
_LOGGER = logging.getLogger(__name__)
SERVER_COURTESY_RESPONSE_SCHEMA = cv.Schema(
@@ -1,10 +1,10 @@
import esphome.codegen as cg
from esphome.components import binary_sensor
from esphome.components.modbus.helpers import MODBUS_REGISTER_TYPE
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_ID
from .. import (
MODBUS_REGISTER_TYPE,
ModbusItemBaseSchema,
SensorItem,
add_modbus_base_properties,
@@ -535,7 +535,7 @@ ModbusCommandItem ModbusCommandItem::create_read_command(
ModbusCommandItem cmd;
cmd.modbusdevice = modbusdevice;
cmd.register_type = register_type;
cmd.function_code = modbus_register_read_function(register_type);
cmd.function_code = modbus::helpers::modbus_register_read_function(register_type);
cmd.register_address = start_address;
cmd.register_count = register_count;
cmd.on_data_func = std::move(handler);
@@ -548,7 +548,7 @@ ModbusCommandItem ModbusCommandItem::create_read_command(ModbusController *modbu
ModbusCommandItem cmd;
cmd.modbusdevice = modbusdevice;
cmd.register_type = register_type;
cmd.function_code = modbus_register_read_function(register_type);
cmd.function_code = modbus::helpers::modbus_register_read_function(register_type);
cmd.register_address = start_address;
cmd.register_count = register_count;
cmd.on_data_func = [modbusdevice](ModbusRegisterType register_type, uint16_t start_address,
@@ -3,6 +3,7 @@
#include "esphome/core/component.h"
#include "esphome/components/modbus/modbus.h"
#include "esphome/components/modbus/modbus_helpers.h"
#include "esphome/core/automation.h"
#include <list>
@@ -19,109 +20,43 @@ class ModbusController;
using modbus::ModbusFunctionCode;
using modbus::ModbusRegisterType;
using modbus::ModbusExceptionCode;
using modbus::helpers::SensorValueType;
enum class SensorValueType : uint8_t {
RAW = 0x00, // variable length
U_WORD = 0x1, // 1 Register unsigned
U_DWORD = 0x2, // 2 Registers unsigned
S_WORD = 0x3, // 1 Register signed
S_DWORD = 0x4, // 2 Registers signed
BIT = 0x5,
U_DWORD_R = 0x6, // 2 Registers unsigned
S_DWORD_R = 0x7, // 2 Registers unsigned
U_QWORD = 0x8,
S_QWORD = 0x9,
U_QWORD_R = 0xA,
S_QWORD_R = 0xB,
FP32 = 0xC,
FP32_R = 0xD
};
inline bool value_type_is_float(SensorValueType v) {
return v == SensorValueType::FP32 || v == SensorValueType::FP32_R;
}
// Remove before 2026.10.0 — these helpers have moved to modbus::helpers
ESPDEPRECATED("Use modbus::helpers::value_type_is_float() instead. Removed in 2026.10.0", "2026.4.0")
inline bool value_type_is_float(SensorValueType v) { return modbus::helpers::value_type_is_float(v); }
ESPDEPRECATED("Use modbus::helpers::modbus_register_read_function() instead. Removed in 2026.10.0", "2026.4.0")
inline ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type) {
switch (reg_type) {
case ModbusRegisterType::COIL:
return ModbusFunctionCode::READ_COILS;
break;
case ModbusRegisterType::DISCRETE_INPUT:
return ModbusFunctionCode::READ_DISCRETE_INPUTS;
break;
case ModbusRegisterType::HOLDING:
return ModbusFunctionCode::READ_HOLDING_REGISTERS;
break;
case ModbusRegisterType::READ:
return ModbusFunctionCode::READ_INPUT_REGISTERS;
break;
default:
return ModbusFunctionCode::CUSTOM;
break;
}
return modbus::helpers::modbus_register_read_function(reg_type);
}
ESPDEPRECATED("Use modbus::helpers::modbus_register_write_function() instead. Removed in 2026.10.0", "2026.4.0")
inline ModbusFunctionCode modbus_register_write_function(ModbusRegisterType reg_type) {
switch (reg_type) {
case ModbusRegisterType::COIL:
return ModbusFunctionCode::WRITE_SINGLE_COIL;
break;
case ModbusRegisterType::DISCRETE_INPUT:
return ModbusFunctionCode::CUSTOM;
break;
case ModbusRegisterType::HOLDING:
return ModbusFunctionCode::READ_WRITE_MULTIPLE_REGISTERS;
break;
case ModbusRegisterType::READ:
default:
return ModbusFunctionCode::CUSTOM;
break;
}
return modbus::helpers::modbus_register_write_function(reg_type);
}
inline uint8_t c_to_hex(char c) { return (c >= 'A') ? (c >= 'a') ? (c - 'a' + 10) : (c - 'A' + 10) : (c - '0'); }
ESPDEPRECATED("Use modbus::helpers::c_to_hex() instead. Removed in 2026.10.0", "2026.4.0")
inline uint8_t c_to_hex(char c) { return modbus::helpers::c_to_hex(c); }
/** Get a byte from a hex string
* hex_byte_from_str("1122",1) returns uint_8 value 0x22 == 34
* hex_byte_from_str("1122",0) returns 0x11
* @param value string containing hex encoding
* @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in
* the hex string is byte_pos * 2
* @return byte value
*/
ESPDEPRECATED("Use modbus::helpers::byte_from_hex_str() instead. Removed in 2026.10.0", "2026.4.0")
inline uint8_t byte_from_hex_str(const std::string &value, uint8_t pos) {
if (value.length() < pos * 2 + 1)
return 0;
return (c_to_hex(value[pos * 2]) << 4) | c_to_hex(value[pos * 2 + 1]);
return modbus::helpers::byte_from_hex_str(value, pos);
}
/** Get a word from a hex string
* @param value string containing hex encoding
* @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in
* the hex string is byte_pos * 2
* @return word value
*/
ESPDEPRECATED("Use modbus::helpers::word_from_hex_str() instead. Removed in 2026.10.0", "2026.4.0")
inline uint16_t word_from_hex_str(const std::string &value, uint8_t pos) {
return byte_from_hex_str(value, pos) << 8 | byte_from_hex_str(value, pos + 1);
return modbus::helpers::word_from_hex_str(value, pos);
}
/** Get a dword from a hex string
* @param value string containing hex encoding
* @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in
* the hex string is byte_pos * 2
* @return dword value
*/
ESPDEPRECATED("Use modbus::helpers::dword_from_hex_str() instead. Removed in 2026.10.0", "2026.4.0")
inline uint32_t dword_from_hex_str(const std::string &value, uint8_t pos) {
return word_from_hex_str(value, pos) << 16 | word_from_hex_str(value, pos + 2);
return modbus::helpers::dword_from_hex_str(value, pos);
}
/** Get a qword from a hex string
* @param value string containing hex encoding
* @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in
* the hex string is byte_pos * 2
* @return qword value
*/
ESPDEPRECATED("Use modbus::helpers::qword_from_hex_str() instead. Removed in 2026.10.0", "2026.4.0")
inline uint64_t qword_from_hex_str(const std::string &value, uint8_t pos) {
return static_cast<uint64_t>(dword_from_hex_str(value, pos)) << 32 | dword_from_hex_str(value, pos + 4);
return modbus::helpers::qword_from_hex_str(value, pos);
}
// Extract data from modbus response buffer
@@ -585,7 +520,7 @@ inline float payload_to_float(const std::vector<uint8_t> &data, const SensorItem
int64_t number = payload_to_number(data, item.sensor_value_type, item.offset, item.bitmask);
float float_value;
if (value_type_is_float(item.sensor_value_type)) {
if (modbus::helpers::value_type_is_float(item.sensor_value_type)) {
float_value = bit_cast<float>(static_cast<uint32_t>(number));
} else {
float_value = static_cast<float>(number);
@@ -597,7 +532,7 @@ inline float payload_to_float(const std::vector<uint8_t> &data, const SensorItem
inline std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type) {
int64_t val;
if (value_type_is_float(value_type)) {
if (modbus::helpers::value_type_is_float(value_type)) {
val = bit_cast<uint32_t>(value);
} else {
val = llroundf(value);
@@ -1,5 +1,9 @@
import esphome.codegen as cg
from esphome.components import number
from esphome.components.modbus.helpers import (
MODBUS_WRITE_REGISTER_TYPE,
SENSOR_VALUE_TYPE,
)
import esphome.config_validation as cv
from esphome.const import (
CONF_ADDRESS,
@@ -11,8 +15,6 @@ from esphome.const import (
)
from .. import (
MODBUS_WRITE_REGISTER_TYPE,
SENSOR_VALUE_TYPE,
ModbusItemBaseSchema,
SensorItem,
add_modbus_base_properties,
@@ -1,10 +1,10 @@
import esphome.codegen as cg
from esphome.components import output
from esphome.components.modbus.helpers import SENSOR_VALUE_TYPE
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_MULTIPLY
from .. import (
SENSOR_VALUE_TYPE,
ModbusItemBaseSchema,
SensorItem,
modbus_calc_properties,
@@ -1,15 +1,10 @@
import esphome.codegen as cg
from esphome.components import select
from esphome.components.modbus.helpers import SENSOR_VALUE_TYPE, TYPE_REGISTER_MAP
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OPTIMISTIC
from .. import (
SENSOR_VALUE_TYPE,
TYPE_REGISTER_MAP,
ModbusController,
SensorItem,
modbus_controller_ns,
)
from .. import ModbusController, SensorItem, modbus_controller_ns
from ..const import (
CONF_FORCE_NEW_RANGE,
CONF_MODBUS_CONTROLLER_ID,
@@ -1,11 +1,10 @@
import esphome.codegen as cg
from esphome.components import sensor
from esphome.components.modbus.helpers import MODBUS_REGISTER_TYPE, SENSOR_VALUE_TYPE
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_ID
from .. import (
MODBUS_REGISTER_TYPE,
SENSOR_VALUE_TYPE,
ModbusItemBaseSchema,
SensorItem,
add_modbus_base_properties,
@@ -1,10 +1,10 @@
import esphome.codegen as cg
from esphome.components import switch
from esphome.components.modbus.helpers import MODBUS_REGISTER_TYPE
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_ASSUMED_STATE, CONF_ID
from .. import (
MODBUS_REGISTER_TYPE,
ModbusItemBaseSchema,
SensorItem,
add_modbus_base_properties,
@@ -1,10 +1,10 @@
import esphome.codegen as cg
from esphome.components import text_sensor
from esphome.components.modbus.helpers import MODBUS_REGISTER_TYPE
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_ID
from .. import (
MODBUS_REGISTER_TYPE,
ModbusItemBaseSchema,
SensorItem,
add_modbus_base_properties,
@@ -21,15 +21,15 @@ class NextionBinarySensor : public NextionComponent,
void process_touch(uint8_t page_id, uint8_t component_id, bool state) override;
// Set the components page id for Nextion Touch Component
void set_page_id(uint8_t page_id) { page_id_ = page_id; }
void set_page_id(uint8_t page_id) { this->page_id_ = page_id; }
// Set the components component id for Nextion Touch Component
void set_component_id(uint8_t component_id) { component_id_ = component_id; }
void set_component_id(uint8_t component_id) { this->component_id_ = component_id; }
void set_state(bool state) override { this->set_state(state, true, true); }
void set_state(bool state, bool publish) override { this->set_state(state, publish, true); }
void set_state(bool state, bool publish, bool send_to_nextion) override;
NextionQueueType get_queue_type() override { return NextionQueueType::BINARY_SENSOR; }
NextionQueueType get_queue_type() const override { return NextionQueueType::BINARY_SENSOR; }
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 != 0, publish, send_to_nextion);
+10 -24
View File
@@ -281,7 +281,7 @@ void Nextion::print_queue_members_() {
ESP_LOGN(TAG, "Queue null");
} else {
ESP_LOGN(TAG, "Queue type: %d:%s, name: %s", i->component->get_queue_type(),
i->component->get_queue_type_string().c_str(), i->component->get_variable_name().c_str());
i->component->get_queue_type_string(), i->component->get_variable_name().c_str());
}
}
ESP_LOGN(TAG, "*******************************************");
@@ -423,7 +423,7 @@ void Nextion::process_nextion_commands_() {
DELIMITER_SIZE)) != std::string::npos) {
#ifdef USE_NEXTION_MAX_COMMANDS_PER_LOOP
if (++commands_processed > this->max_commands_per_loop_) {
ESP_LOGW(TAG, "Command processing limit exceeded");
ESP_LOGV(TAG, "Command limit reached, deferring");
break;
}
#endif // USE_NEXTION_MAX_COMMANDS_PER_LOOP
@@ -607,7 +607,7 @@ void Nextion::process_nextion_commands_() {
ESP_LOGE(TAG, "String return but '%s' not text sensor", component->get_variable_name().c_str());
} else {
ESP_LOGN(TAG, "String resp: '%s' id: %s type: %s", to_process.c_str(), component->get_variable_name().c_str(),
component->get_queue_type_string().c_str());
component->get_queue_type_string());
}
delete nb; // NOLINT(cppcoreguidelines-owning-memory)
@@ -649,7 +649,7 @@ void Nextion::process_nextion_commands_() {
component->get_queue_type());
} else {
ESP_LOGN(TAG, "Numeric: %s type %d:%s val %d", component->get_variable_name().c_str(),
component->get_queue_type(), component->get_queue_type_string().c_str(), value);
component->get_queue_type(), component->get_queue_type_string(), value);
component->set_state_from_int(value, true, false);
}
@@ -842,24 +842,10 @@ void Nextion::process_nextion_commands_() {
if (this->max_q_age_ms_ > 0 && !this->nextion_queue_.empty() &&
ms - this->nextion_queue_.front()->queue_time > this->max_q_age_ms_) {
for (auto it = this->nextion_queue_.begin(); it != this->nextion_queue_.end();) {
NextionComponentBase *component = (*it)->component;
if (ms - (*it)->queue_time > this->max_q_age_ms_) {
if ((*it)->queue_time == 0) {
ESP_LOGD(TAG, "Remove old queue '%s':'%s' (t=0)", component->get_queue_type_string().c_str(),
component->get_variable_name().c_str());
}
if (component->get_variable_name() == "sleep_wake") {
this->is_sleeping_ = false;
}
if ((*it)->pending_command.empty()) {
ESP_LOGD(TAG, "Remove old queue '%s':'%s'", component->get_queue_type_string().c_str(),
component->get_variable_name().c_str());
} else {
ESP_LOGD(TAG, "Remove old queue '%s':'%s' cmd:'%s'", component->get_queue_type_string().c_str(),
component->get_variable_name().c_str(), (*it)->pending_command.c_str());
}
NextionComponentBase *component = (*it)->component;
ESP_LOGV(TAG, "Remove old queue '%s':'%s'", component->get_queue_type_string(),
component->get_variable_name().c_str());
if (component->get_queue_type() == NextionQueueType::NO_RESULT) {
if (component->get_variable_name() == "sleep_wake") {
@@ -940,7 +926,7 @@ 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_chan_id() == 0)
if ((force_update || sensortype->get_needs_to_send_update()) && sensortype->get_wave_channel_id() == 0)
sensortype->send_state_to_nextion();
}
for (auto *switchtype : this->switchtype_) {
@@ -1048,7 +1034,7 @@ void Nextion::add_no_result_to_queue_(const std::string &variable_name) {
nextion_queue->component = new nextion::NextionComponentBase;
nextion_queue->component->set_variable_name(variable_name);
nextion_queue->queue_time = millis();
nextion_queue->queue_time = App.get_loop_component_start_time();
this->nextion_queue_.push_back(nextion_queue);
@@ -1236,7 +1222,7 @@ void Nextion::add_to_get_queue(NextionComponentBase *component) {
nextion_queue->component = component;
nextion_queue->queue_time = App.get_loop_component_start_time();
ESP_LOGN(TAG, "Queue %s: %s", component->get_queue_type_string().c_str(), component->get_variable_name().c_str());
ESP_LOGN(TAG, "Queue %s: %s", component->get_queue_type_string(), component->get_variable_name().c_str());
std::string command = "get " + component->get_variable_name_to_send();
@@ -1,4 +1,6 @@
#pragma once
#include <string>
#include <utility>
#include <vector>
#include "esphome/core/defines.h"
@@ -35,12 +37,8 @@ class NextionComponentBase {
virtual ~NextionComponentBase() = default;
void set_variable_name(const std::string &variable_name, const std::string &variable_name_to_send = "") {
variable_name_ = variable_name;
if (variable_name_to_send.empty()) {
variable_name_to_send_ = variable_name;
} else {
variable_name_to_send_ = variable_name_to_send;
}
this->variable_name_ = variable_name;
this->variable_name_to_send_ = variable_name_to_send.empty() ? variable_name : variable_name_to_send;
}
virtual void update_component_settings(){};
@@ -64,14 +62,14 @@ class NextionComponentBase {
virtual void set_state(const std::string &state, bool publish) {}
virtual void set_state(const std::string &state, bool publish, bool send_to_nextion){};
uint8_t get_component_id() { return this->component_id_; }
void set_component_id(uint8_t component_id) { component_id_ = component_id; }
uint8_t get_component_id() const { return this->component_id_; }
void set_component_id(uint8_t component_id) { this->component_id_ = component_id; }
uint8_t get_wave_channel_id() { return this->wave_chan_id_; }
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; }
std::vector<uint8_t> get_wave_buffer() { return this->wave_buffer_; }
size_t get_wave_buffer_size() { return this->wave_buffer_.size(); }
const std::vector<uint8_t> &get_wave_buffer() const { return this->wave_buffer_; }
size_t get_wave_buffer_size() const { return this->wave_buffer_.size(); }
void clear_wave_buffer(size_t buffer_sent) {
if (this->wave_buffer_.size() <= buffer_sent) {
this->wave_buffer_.clear();
@@ -80,15 +78,17 @@ class NextionComponentBase {
}
}
std::string get_variable_name() { return this->variable_name_; }
std::string get_variable_name_to_send() { return this->variable_name_to_send_; }
virtual NextionQueueType get_queue_type() { return NextionQueueType::NO_RESULT; }
virtual std::string get_queue_type_string() { return NEXTION_QUEUE_TYPE_STRINGS[this->get_queue_type()]; }
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_; }
virtual NextionQueueType get_queue_type() const { return NextionQueueType::NO_RESULT; }
virtual const char *get_queue_type_string() const { return NEXTION_QUEUE_TYPE_STRINGS[this->get_queue_type()]; }
virtual void set_state_from_int(int state_value, bool publish, bool send_to_nextion){};
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() { return this->needs_to_send_update_; }
uint8_t get_wave_chan_id() { return this->wave_chan_id_; }
bool get_needs_to_send_update() const { return this->needs_to_send_update_; }
// 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; }
protected:
@@ -17,7 +17,7 @@ class NextionSensor : public NextionComponent, public sensor::Sensor, public Pol
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) { component_id_ = component_id; }
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;
@@ -27,9 +27,8 @@ class NextionSensor : public NextionComponent, public sensor::Sensor, public Pol
void set_state(float state, bool publish, bool send_to_nextion) override;
void set_waveform_send_last_value(bool send_last_value) { this->send_last_value_ = send_last_value; }
uint8_t get_wave_chan_id() { return this->wave_chan_id_; }
void set_wave_max_length(int wave_max_length) { this->wave_max_length_ = wave_max_length; }
NextionQueueType get_queue_type() override {
NextionQueueType get_queue_type() const override {
return this->wave_chan_id_ == UINT8_MAX ? NextionQueueType::SENSOR : NextionQueueType::WAVEFORM_SENSOR;
}
void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {}
@@ -21,7 +21,7 @@ class NextionSwitch : public NextionComponent, public switch_::Switch, public Po
void set_state(bool state, bool publish, bool send_to_nextion) override;
void send_state_to_nextion() override { this->set_state(this->state, false, true); };
NextionQueueType get_queue_type() override { return NextionQueueType::SWITCH; }
NextionQueueType get_queue_type() const override { return NextionQueueType::SWITCH; }
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 != 0, publish, send_to_nextion);
@@ -22,7 +22,7 @@ class NextionTextSensor : public NextionComponent, public text_sensor::TextSenso
void set_state(const std::string &state, bool publish, bool send_to_nextion) override;
void send_state_to_nextion() override { this->set_state(this->state, false, true); };
NextionQueueType get_queue_type() override { return NextionQueueType::TEXT_SENSOR; }
NextionQueueType get_queue_type() const override { return NextionQueueType::TEXT_SENSOR; }
void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override {}
void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {
this->set_state(state_value, publish, send_to_nextion);
+7 -11
View File
@@ -2,16 +2,15 @@
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace shtcx {
namespace esphome::shtcx {
static const char *const TAG = "shtcx";
static const uint16_t SHTCX_COMMAND_SLEEP = 0xB098;
static const uint16_t SHTCX_COMMAND_WAKEUP = 0x3517;
static const uint16_t SHTCX_COMMAND_READ_ID_REGISTER = 0xEFC8;
static const uint16_t SHTCX_COMMAND_SOFT_RESET = 0x805D;
static const uint16_t SHTCX_COMMAND_POLLING_H = 0x7866;
static constexpr uint16_t SHTCX_COMMAND_SLEEP = 0xB098;
static constexpr uint16_t SHTCX_COMMAND_WAKEUP = 0x3517;
static constexpr uint16_t SHTCX_COMMAND_READ_ID_REGISTER = 0xEFC8;
static constexpr uint16_t SHTCX_COMMAND_SOFT_RESET = 0x805D;
static constexpr uint16_t SHTCX_COMMAND_POLLING_H = 0x7866;
static const LogString *shtcx_type_to_string(SHTCXType type) {
switch (type) {
@@ -91,8 +90,6 @@ void SHTCXComponent::update() {
} else {
temperature = 175.0f * float(raw_data[0]) / 65536.0f - 45.0f;
humidity = 100.0f * float(raw_data[1]) / 65536.0f;
ESP_LOGD(TAG, "Temperature=%.2f°C Humidity=%.2f%%", temperature, humidity);
}
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature);
@@ -117,5 +114,4 @@ void SHTCXComponent::wake_up() {
delayMicroseconds(200);
}
} // namespace shtcx
} // namespace esphome
} // namespace esphome::shtcx
+9 -7
View File
@@ -4,10 +4,13 @@
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/sensirion_common/i2c_sensirion.h"
namespace esphome {
namespace shtcx {
namespace esphome::shtcx {
enum SHTCXType { SHTCX_TYPE_SHTC3 = 0, SHTCX_TYPE_SHTC1, SHTCX_TYPE_UNKNOWN };
enum SHTCXType : uint8_t {
SHTCX_TYPE_SHTC3 = 0,
SHTCX_TYPE_SHTC1,
SHTCX_TYPE_UNKNOWN,
};
/// This class implements support for the SHT3x-DIS family of temperature+humidity i2c sensors.
class SHTCXComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice {
@@ -23,11 +26,10 @@ class SHTCXComponent : public PollingComponent, public sensirion_common::Sensiri
void wake_up();
protected:
SHTCXType type_;
uint16_t sensor_id_;
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
uint16_t sensor_id_;
SHTCXType type_;
};
} // namespace shtcx
} // namespace esphome
} // namespace esphome::shtcx
@@ -606,6 +606,16 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
}
void ThermostatClimate::switch_to_supplemental_action_(climate::ClimateAction action) {
// Always cancel max-runtime timers and clear exceeded flags when transitioning to idle/off,
// even if supplemental_action_ is already idle (early-return path). This prevents a stale
// heating_max_runtime_exceeded_ flag from triggering supplemental on the next heating cycle
// when HEATING_MAX_RUN_TIME fires while the main action is already IDLE.
if (action == climate::CLIMATE_ACTION_OFF || action == climate::CLIMATE_ACTION_IDLE) {
this->cancel_timer_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME);
this->cancel_timer_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME);
this->cooling_max_runtime_exceeded_ = false;
this->heating_max_runtime_exceeded_ = false;
}
// setup_complete_ helps us ensure an action is called immediately after boot
if ((action == this->supplemental_action_) && this->setup_complete_) {
// already in target mode
@@ -975,8 +985,10 @@ void ThermostatClimate::cooling_on_timer_callback_() {
void ThermostatClimate::fan_mode_timer_callback_() {
ESP_LOGVV(TAG, "fan_mode timer expired");
this->switch_to_fan_mode_(this->fan_mode.value_or(climate::CLIMATE_FAN_ON));
if (this->supports_fan_only_action_uses_fan_mode_timer_)
if (this->supports_fan_only_action_uses_fan_mode_timer_) {
this->switch_to_action_(this->compute_action_());
this->switch_to_supplemental_action_(this->compute_supplemental_action_());
}
}
void ThermostatClimate::fanning_off_timer_callback_() {
+11
View File
@@ -6,6 +6,9 @@
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#ifdef USE_ESP32
#include "esphome/core/lock_free_queue.h"
#endif
#include "esphome/core/string_ref.h"
#include <span>
@@ -727,6 +730,7 @@ class WiFiComponent final : public Component {
#ifdef USE_ESP32
void wifi_process_event_(IDFWiFiEvent *data);
friend void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
#endif
#ifdef USE_RP2040
@@ -871,6 +875,13 @@ class WiFiComponent final : public Component {
bool is_high_performance_mode_{false};
#endif
#ifdef USE_ESP32
// Lock-free SPSC queue for WiFi events from ESP-IDF event handler.
// 17 slots = 16 usable (ring buffer reserves one slot). WiFi events are rare.
// Placed at end of class to avoid padding between smaller fields.
LockFreeQueue<IDFWiFiEvent, 17> event_queue_;
#endif
private:
// Stores a pointer to a string literal (static storage duration).
// ONLY set from Python-generated code with string literals - never dynamic strings.
@@ -47,7 +47,6 @@ namespace esphome::wifi {
static const char *const TAG = "wifi_esp32";
static EventGroupHandle_t s_wifi_event_group; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static QueueHandle_t s_event_queue; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static esp_netif_t *s_sta_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
#ifdef USE_WIFI_AP
static esp_netif_t *s_ap_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
@@ -132,11 +131,10 @@ void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, voi
return;
}
// copy to heap to keep queue object small
// copy to heap — WiFi events are rare so heap alloc is fine
auto *to_send = new IDFWiFiEvent; // NOLINT(cppcoreguidelines-owning-memory)
memcpy(to_send, &event, sizeof(IDFWiFiEvent));
// don't block, we may miss events but the core can handle that
if (xQueueSend(s_event_queue, &to_send, 0L) != pdPASS) {
if (!global_wifi_component->event_queue_.push(to_send)) {
delete to_send; // NOLINT(cppcoreguidelines-owning-memory)
}
}
@@ -157,12 +155,6 @@ void WiFiComponent::wifi_pre_setup_() {
ESP_LOGE(TAG, "xEventGroupCreate failed");
return;
}
// NOLINTNEXTLINE(bugprone-sizeof-expression)
s_event_queue = xQueueCreate(64, sizeof(IDFWiFiEvent *));
if (s_event_queue == nullptr) {
ESP_LOGE(TAG, "xQueueCreate failed");
return;
}
err = esp_event_loop_create_default();
if (err != ERR_OK) {
ESP_LOGE(TAG, "esp_event_loop_create_default failed: %s", esp_err_to_name(err));
@@ -724,16 +716,14 @@ const char *get_disconnect_reason_str(uint8_t reason) {
}
void WiFiComponent::wifi_loop_() {
while (true) {
IDFWiFiEvent *data;
if (xQueueReceive(s_event_queue, &data, 0L) != pdTRUE) {
// no event ready
break;
}
uint16_t dropped = this->event_queue_.get_and_reset_dropped_count();
if (dropped > 0) {
ESP_LOGW(TAG, "Dropped %u WiFi events due to buffer overflow", dropped);
}
// process event
IDFWiFiEvent *data;
while ((data = this->event_queue_.pop()) != nullptr) {
wifi_process_event_(data);
delete data; // NOLINT(cppcoreguidelines-owning-memory)
}
}
+21 -18
View File
@@ -419,44 +419,48 @@ template<typename... Ts> class Action {
template<typename... Ts> class ActionList {
public:
void add_action(Action<Ts...> *action) {
if (this->actions_end_ == nullptr) {
this->actions_begin_ = action;
} else {
this->actions_end_->next_ = action;
}
this->actions_end_ = action;
// Walk to end of chain - action lists are short and only built during setup()
Action<Ts...> **tail = &this->actions_;
while (*tail != nullptr)
tail = &(*tail)->next_;
*tail = action;
}
void add_actions(const std::initializer_list<Action<Ts...> *> &actions) {
// Find tail once, then append all actions in a single pass
Action<Ts...> **tail = &this->actions_;
while (*tail != nullptr)
tail = &(*tail)->next_;
for (auto *action : actions) {
this->add_action(action);
*tail = action;
tail = &action->next_;
}
}
// Force-inline: part of the Trigger→Automation→ActionList forwarding
// chain collapsed to reduce automation call stack depth.
inline void play(const Ts &...x) ESPHOME_ALWAYS_INLINE {
if (this->actions_begin_ != nullptr)
this->actions_begin_->play_complex(x...);
if (this->actions_ != nullptr)
this->actions_->play_complex(x...);
}
void play_tuple(const std::tuple<Ts...> &tuple) {
this->play_tuple_(tuple, std::make_index_sequence<sizeof...(Ts)>{});
}
void stop() {
if (this->actions_begin_ != nullptr)
this->actions_begin_->stop_complex();
if (this->actions_ != nullptr)
this->actions_->stop_complex();
}
bool empty() const { return this->actions_begin_ == nullptr; }
bool empty() const { return this->actions_ == nullptr; }
/// Check if any action in this action list is currently running.
bool is_running() {
if (this->actions_begin_ == nullptr)
if (this->actions_ == nullptr)
return false;
return this->actions_begin_->is_running();
return this->actions_->is_running();
}
/// Return the number of actions in this action list that are currently running.
int num_running() {
if (this->actions_begin_ == nullptr)
if (this->actions_ == nullptr)
return 0;
return this->actions_begin_->num_running_total();
return this->actions_->num_running_total();
}
protected:
@@ -464,8 +468,7 @@ template<typename... Ts> class ActionList {
this->play(std::get<S>(tuple)...);
}
Action<Ts...> *actions_begin_{nullptr};
Action<Ts...> *actions_end_{nullptr};
Action<Ts...> *actions_{nullptr};
};
template<typename... Ts> class Automation {
+32
View File
@@ -1830,6 +1830,38 @@ template<typename... Ts> class CallbackManager<void(Ts...)> {
std::vector<Callback<void(Ts...)>> callbacks_;
};
/** CallbackManager backed by StaticVector for compile-time-known callback counts.
*
* Drop-in replacement for CallbackManager that avoids std::vector template bloat
* (_M_realloc_insert, etc.) when the maximum number of callbacks is known at compile time.
*
* @tparam N Maximum number of callbacks (compile-time constant, typically from cg.add_define())
* @tparam Ts The arguments for the callbacks, wrapped in void().
*/
template<size_t N, typename... X> class StaticCallbackManager;
template<size_t N, typename... Ts> class StaticCallbackManager<N, void(Ts...)> {
public:
/// Add any callable. Small trivially-copyable callables (like [this] lambdas)
/// are stored inline without heap allocation.
template<typename F> void add(F &&callback) { this->add_(Callback<void(Ts...)>::create(std::forward<F>(callback))); }
/// Call all callbacks in this manager.
void call(Ts... args) {
for (auto &cb : this->callbacks_)
cb.call(args...);
}
size_t size() const { return this->callbacks_.size(); }
/// Call all callbacks in this manager.
void operator()(Ts... args) { call(args...); }
protected:
/// Non-template core to avoid code duplication per lambda type.
void add_(Callback<void(Ts...)> cb) { this->callbacks_.push_back(cb); }
StaticVector<Callback<void(Ts...)>, N> callbacks_;
};
template<typename... X> class LazyCallbackManager;
/** Lazy-allocating callback manager that only allocates memory when callbacks are registered.
+1 -1
View File
@@ -12,7 +12,7 @@ platformio==6.1.19
esptool==5.2.0
click==8.3.1
esphome-dashboard==20260210.0
aioesphomeapi==44.8.0
aioesphomeapi==44.8.1
zeroconf==0.148.0
puremagic==1.30
ruamel.yaml==0.19.1 # dashboard_import
@@ -10,10 +10,9 @@ namespace esphome::api::benchmarks {
// sub-microsecond benchmarks.
static constexpr int kInnerIterations = 2000;
// Helper: encode a message into a buffer and return it.
// Benchmarks encode once in setup, then decode the resulting bytes in a loop.
// This keeps decode benchmarks in sync with the actual protobuf schema —
// hand-encoded byte arrays would silently break when fields change.
// Helper: encode a message into an APIBuffer for reuse in decode benchmarks.
// Optimization barriers are applied to the decode target objects via
// DoNotOptimize/ClobberMemory, not to this buffer.
template<typename T> static APIBuffer encode_message(const T &msg) {
APIBuffer buffer;
uint32_t size = msg.calculate_size();
@@ -23,6 +22,12 @@ template<typename T> static APIBuffer encode_message(const T &msg) {
return buffer;
}
/// Force a pointer through an asm barrier so the compiler cannot
/// prove its contents are unchanged across iterations.
/// benchmark::DoNotOptimize/ClobberMemory are insufficient under
/// CodSpeed's valgrind-based instrumentation.
static void escape(void *p) { asm volatile("" : : "g"(p) : "memory"); }
// --- HelloRequest decode (string + varint fields) ---
static void Decode_HelloRequest(benchmark::State &state) {
@@ -31,13 +36,18 @@ static void Decode_HelloRequest(benchmark::State &state) {
source.api_version_major = 1;
source.api_version_minor = 10;
auto encoded = encode_message(source);
auto *data = encoded.data();
auto size = encoded.size();
benchmark::DoNotOptimize(data);
benchmark::DoNotOptimize(size);
for (auto _ : state) {
HelloRequest msg;
for (int i = 0; i < kInnerIterations; i++) {
msg.decode(encoded.data(), encoded.size());
HelloRequest msg;
escape(&msg);
msg.decode(data, size);
escape(&msg);
}
benchmark::DoNotOptimize(msg.api_version_major);
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
@@ -50,13 +60,18 @@ static void Decode_SwitchCommandRequest(benchmark::State &state) {
source.key = 0x12345678;
source.state = true;
auto encoded = encode_message(source);
auto *data = encoded.data();
auto size = encoded.size();
benchmark::DoNotOptimize(data);
benchmark::DoNotOptimize(size);
for (auto _ : state) {
SwitchCommandRequest msg;
for (int i = 0; i < kInnerIterations; i++) {
msg.decode(encoded.data(), encoded.size());
SwitchCommandRequest msg;
escape(&msg);
msg.decode(data, size);
escape(&msg);
}
benchmark::DoNotOptimize(msg.state);
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
@@ -78,13 +93,18 @@ static void Decode_LightCommandRequest(benchmark::State &state) {
source.has_effect = true;
source.effect = StringRef::from_lit("rainbow");
auto encoded = encode_message(source);
auto *data = encoded.data();
auto size = encoded.size();
benchmark::DoNotOptimize(data);
benchmark::DoNotOptimize(size);
for (auto _ : state) {
LightCommandRequest msg;
for (int i = 0; i < kInnerIterations; i++) {
msg.decode(encoded.data(), encoded.size());
LightCommandRequest msg;
escape(&msg);
msg.decode(data, size);
escape(&msg);
}
benchmark::DoNotOptimize(msg.brightness);
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}