Compare commits

..

6 Commits

Author SHA1 Message Date
J. Nick Koston
ae5a3e616a improve comment 2026-01-17 16:26:18 -10:00
J. Nick Koston
759278191b simpler 2026-01-17 16:23:30 -10:00
J. Nick Koston
4549e375c1 adjust 2026-01-17 16:16:58 -10:00
J. Nick Koston
a8b07af2a3 fixes 2026-01-17 16:12:51 -10:00
J. Nick Koston
f003fac5d8 document, document, document 2026-01-17 15:51:45 -10:00
J. Nick Koston
6b02f5dfbd [esp32_ble_tracker] Optimize loop with state change tracking for ~85% CPU reduction 2026-01-17 15:47:37 -10:00
114 changed files with 524 additions and 1279 deletions

View File

@@ -69,7 +69,6 @@ from esphome.cpp_types import ( # noqa: F401
JsonObjectConst,
Parented,
PollingComponent,
StringRef,
arduino_json_ns,
bool_,
const_char_ptr,

View File

@@ -18,31 +18,31 @@ AnovaPacket *AnovaCodec::clean_packet_() {
AnovaPacket *AnovaCodec::get_read_device_status_request() {
this->current_query_ = READ_DEVICE_STATUS;
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_DEVICE_STATUS);
sprintf((char *) this->packet_.data, "%s", CMD_READ_DEVICE_STATUS);
return this->clean_packet_();
}
AnovaPacket *AnovaCodec::get_read_target_temp_request() {
this->current_query_ = READ_TARGET_TEMPERATURE;
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_TARGET_TEMP);
sprintf((char *) this->packet_.data, "%s", CMD_READ_TARGET_TEMP);
return this->clean_packet_();
}
AnovaPacket *AnovaCodec::get_read_current_temp_request() {
this->current_query_ = READ_CURRENT_TEMPERATURE;
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_CURRENT_TEMP);
sprintf((char *) this->packet_.data, "%s", CMD_READ_CURRENT_TEMP);
return this->clean_packet_();
}
AnovaPacket *AnovaCodec::get_read_unit_request() {
this->current_query_ = READ_UNIT;
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_UNIT);
sprintf((char *) this->packet_.data, "%s", CMD_READ_UNIT);
return this->clean_packet_();
}
AnovaPacket *AnovaCodec::get_read_data_request() {
this->current_query_ = READ_DATA;
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_READ_DATA);
sprintf((char *) this->packet_.data, "%s", CMD_READ_DATA);
return this->clean_packet_();
}
@@ -50,25 +50,25 @@ AnovaPacket *AnovaCodec::get_set_target_temp_request(float temperature) {
this->current_query_ = SET_TARGET_TEMPERATURE;
if (this->fahrenheit_)
temperature = ctof(temperature);
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), CMD_SET_TARGET_TEMP, temperature);
sprintf((char *) this->packet_.data, CMD_SET_TARGET_TEMP, temperature);
return this->clean_packet_();
}
AnovaPacket *AnovaCodec::get_set_unit_request(char unit) {
this->current_query_ = SET_UNIT;
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), CMD_SET_TEMP_UNIT, unit);
sprintf((char *) this->packet_.data, CMD_SET_TEMP_UNIT, unit);
return this->clean_packet_();
}
AnovaPacket *AnovaCodec::get_start_request() {
this->current_query_ = START;
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_START);
sprintf((char *) this->packet_.data, CMD_START);
return this->clean_packet_();
}
AnovaPacket *AnovaCodec::get_stop_request() {
this->current_query_ = STOP;
snprintf((char *) this->packet_.data, sizeof(this->packet_.data), "%s", CMD_STOP);
sprintf((char *) this->packet_.data, CMD_STOP);
return this->clean_packet_();
}

View File

@@ -1712,16 +1712,17 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes
}
// Create null-terminated state for callback (parse_number needs null-termination)
// HA state max length is 255 characters, but attributes can be much longer
// Use stack buffer for common case (states), heap fallback for large attributes
size_t state_len = msg.state.size();
SmallBufferWithHeapFallback<MAX_STATE_LEN + 1> state_buf_alloc(state_len + 1);
char *state_buf = reinterpret_cast<char *>(state_buf_alloc.get());
if (state_len > 0) {
memcpy(state_buf, msg.state.c_str(), state_len);
// HA state max length is 255, so 256 byte buffer covers all cases
char state_buf[256];
size_t copy_len = msg.state.size();
if (copy_len >= sizeof(state_buf)) {
copy_len = sizeof(state_buf) - 1; // Truncate to leave space for null terminator
}
state_buf[state_len] = '\0';
it.callback(StringRef(state_buf, state_len));
if (copy_len > 0) {
memcpy(state_buf, msg.state.c_str(), copy_len);
}
state_buf[copy_len] = '\0';
it.callback(StringRef(state_buf, copy_len));
}
}
#endif

View File

@@ -25,9 +25,7 @@ template<typename... X> class TemplatableStringValue : public TemplatableValue<s
private:
// Helper to convert value to string - handles the case where value is already a string
template<typename T> static std::string value_to_string(T &&val) {
return to_string(std::forward<T>(val));
} // NOLINT
template<typename T> static std::string value_to_string(T &&val) { return to_string(std::forward<T>(val)); }
// Overloads for string types - needed because std::to_string doesn't support them
static std::string value_to_string(char *val) {

View File

@@ -158,14 +158,12 @@ void ATM90E32Component::setup() {
if (this->enable_offset_calibration_) {
// Initialize flash storage for offset calibrations
uint32_t o_hash = fnv1_hash("_offset_calibration_");
o_hash = fnv1_hash_extend(o_hash, this->cs_summary_);
uint32_t o_hash = fnv1_hash(std::string("_offset_calibration_") + this->cs_summary_);
this->offset_pref_ = global_preferences->make_preference<OffsetCalibration[3]>(o_hash, true);
this->restore_offset_calibrations_();
// Initialize flash storage for power offset calibrations
uint32_t po_hash = fnv1_hash("_power_offset_calibration_");
po_hash = fnv1_hash_extend(po_hash, this->cs_summary_);
uint32_t po_hash = fnv1_hash(std::string("_power_offset_calibration_") + this->cs_summary_);
this->power_offset_pref_ = global_preferences->make_preference<PowerOffsetCalibration[3]>(po_hash, true);
this->restore_power_offset_calibrations_();
} else {
@@ -185,8 +183,7 @@ void ATM90E32Component::setup() {
if (this->enable_gain_calibration_) {
// Initialize flash storage for gain calibration
uint32_t g_hash = fnv1_hash("_gain_calibration_");
g_hash = fnv1_hash_extend(g_hash, this->cs_summary_);
uint32_t g_hash = fnv1_hash(std::string("_gain_calibration_") + this->cs_summary_);
this->gain_calibration_pref_ = global_preferences->make_preference<GainCalibration[3]>(g_hash, true);
this->restore_gain_calibrations_();

View File

@@ -185,16 +185,18 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) {
return err;
}
if (str_endswith_ignore_case(url, ".wav")) {
std::string url_string = str_lower_case(url);
if (str_endswith(url_string, ".wav")) {
file_type = AudioFileType::WAV;
}
#ifdef USE_AUDIO_MP3_SUPPORT
else if (str_endswith_ignore_case(url, ".mp3")) {
else if (str_endswith(url_string, ".mp3")) {
file_type = AudioFileType::MP3;
}
#endif
#ifdef USE_AUDIO_FLAC_SUPPORT
else if (str_endswith_ignore_case(url, ".flac")) {
else if (str_endswith(url_string, ".flac")) {
file_type = AudioFileType::FLAC;
}
#endif

View File

@@ -135,8 +135,8 @@ void BluetoothConnection::loop() {
// - For V3_WITH_CACHE: Services are never sent, disable after INIT state
// - For V3_WITHOUT_CACHE: Disable only after service discovery is complete
// (send_service_ == DONE_SENDING_SERVICES, which is only set after services are sent)
if (this->state_ != espbt::ClientState::INIT && (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
this->send_service_ == DONE_SENDING_SERVICES)) {
if (this->state() != espbt::ClientState::INIT && (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
this->send_service_ == DONE_SENDING_SERVICES)) {
this->disable_loop();
}
}

View File

@@ -81,8 +81,8 @@ void CCS811Component::setup() {
bootloader_version, application_version);
if (this->version_ != nullptr) {
char version[20]; // "15.15.15 (0xffff)" is 17 chars, plus NUL, plus wiggle room
buf_append_printf(version, sizeof(version), 0, "%d.%d.%d (0x%02x)", (application_version >> 12 & 15),
(application_version >> 8 & 15), (application_version >> 4 & 15), application_version);
sprintf(version, "%d.%d.%d (0x%02x)", (application_version >> 12 & 15), (application_version >> 8 & 15),
(application_version >> 4 & 15), application_version);
ESP_LOGD(TAG, "publishing version state: %s", version);
this->version_->publish_state(version);
}

View File

@@ -133,7 +133,7 @@ bool CH422GGPIOPin::digital_read() { return this->parent_->digital_read(this->pi
void CH422GGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value ^ this->inverted_); }
size_t CH422GGPIOPin::dump_summary(char *buffer, size_t len) const {
return buf_append_printf(buffer, len, 0, "EXIO%u via CH422G", this->pin_);
return snprintf(buffer, len, "EXIO%u via CH422G", this->pin_);
}
void CH422GGPIOPin::set_flags(gpio::Flags flags) {
flags_ = flags;

View File

@@ -207,24 +207,20 @@ void CSE7766Component::parse_data_() {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
{
// Buffer: 7 + 15 + 33 + 15 + 25 = 95 chars max + null, rounded to 128 for safety margin.
// Float sizes with %.4f can be up to 11 chars for large values (e.g., 999999.9999).
char buf[128];
size_t pos = buf_append_printf(buf, sizeof(buf), 0, "Parsed:");
std::string buf = "Parsed:";
if (have_voltage) {
pos = buf_append_printf(buf, sizeof(buf), pos, " V=%.4fV", voltage);
buf += str_sprintf(" V=%fV", voltage);
}
if (have_current) {
pos = buf_append_printf(buf, sizeof(buf), pos, " I=%.4fmA (~%.4fmA)", current * 1000.0f,
calculated_current * 1000.0f);
buf += str_sprintf(" I=%fmA (~%fmA)", current * 1000.0f, calculated_current * 1000.0f);
}
if (have_power) {
pos = buf_append_printf(buf, sizeof(buf), pos, " P=%.4fW", power);
buf += str_sprintf(" P=%fW", power);
}
if (energy != 0.0f) {
buf_append_printf(buf, sizeof(buf), pos, " E=%.4fkWh (%u)", energy, cf_pulses);
buf += str_sprintf(" E=%fkWh (%u)", energy, cf_pulses);
}
ESP_LOGVV(TAG, "%s", buf);
ESP_LOGVV(TAG, "%s", buf.c_str());
}
#endif
}

View File

@@ -258,9 +258,8 @@ bool DaikinArcClimate::parse_state_frame_(const uint8_t frame[]) {
}
char buf[DAIKIN_STATE_FRAME_SIZE * 3 + 1] = {0};
size_t pos = 0;
for (size_t i = 0; i < DAIKIN_STATE_FRAME_SIZE; i++) {
pos = buf_append_printf(buf, sizeof(buf), pos, "%02x ", frame[i]);
sprintf(buf, "%s%02x ", buf, frame[i]);
}
ESP_LOGD(TAG, "FRAME %s", buf);
@@ -350,9 +349,8 @@ bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) {
if (data.expect_item(DAIKIN_HEADER_MARK, DAIKIN_HEADER_SPACE)) {
valid_daikin_frame = true;
size_t bytes_count = data.size() / 2 / 8;
size_t buf_size = bytes_count * 3 + 1;
std::unique_ptr<char[]> buf(new char[buf_size]()); // value-initialize (zero-fill)
size_t buf_pos = 0;
std::unique_ptr<char[]> buf(new char[bytes_count * 3 + 1]);
buf[0] = '\0';
for (size_t i = 0; i < bytes_count; i++) {
uint8_t byte = 0;
for (int8_t bit = 0; bit < 8; bit++) {
@@ -363,19 +361,19 @@ bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) {
break;
}
}
buf_pos = buf_append_printf(buf.get(), buf_size, buf_pos, "%02x ", byte);
sprintf(buf.get(), "%s%02x ", buf.get(), byte);
}
ESP_LOGD(TAG, "WHOLE FRAME %s size: %d", buf.get(), data.size());
}
if (!valid_daikin_frame) {
char sbuf[16 * 10 + 1] = {0};
size_t sbuf_pos = 0;
char sbuf[16 * 10 + 1];
sbuf[0] = '\0';
for (size_t j = 0; j < static_cast<size_t>(data.size()); j++) {
if ((j - 2) % 16 == 0) {
if (j > 0) {
ESP_LOGD(TAG, "DATA %04x: %s", (j - 16 > 0xffff ? 0 : j - 16), sbuf);
}
sbuf_pos = 0;
sbuf[0] = '\0';
}
char type_ch = ' ';
// debug_tolerance = 25%
@@ -403,10 +401,9 @@ bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) {
type_ch = '0';
if (abs(data[j]) > 100000) {
sbuf_pos = buf_append_printf(sbuf, sizeof(sbuf), sbuf_pos, "%-5d[%c] ", data[j] > 0 ? 99999 : -99999, type_ch);
sprintf(sbuf, "%s%-5d[%c] ", sbuf, data[j] > 0 ? 99999 : -99999, type_ch);
} else {
sbuf_pos =
buf_append_printf(sbuf, sizeof(sbuf), sbuf_pos, "%-5d[%c] ", (int) (round(data[j] / 10.) * 10), type_ch);
sprintf(sbuf, "%s%-5d[%c] ", sbuf, (int) (round(data[j] / 10.) * 10), type_ch);
}
if (j + 1 == static_cast<size_t>(data.size())) {
ESP_LOGD(TAG, "DATA %04x: %s", (j - 8 > 0xffff ? 0 : j - 8), sbuf);

View File

@@ -127,9 +127,7 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
this->min2_ = min2 = this->max2_ = max2 = this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 =
this->max4_ = max4 = -1;
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f", min1 / 0.15, max1 / 0.15);
this->cmd_ = buf;
this->cmd_ = str_sprintf("detRangeCfg -1 %.0f %.0f", min1 / 0.15, max1 / 0.15);
} else if (min3 < 0 || max3 < 0) {
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
@@ -137,10 +135,7 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
this->max2_ = max2 = round(max2 / 0.15) * 0.15;
this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 = this->max4_ = max4 = -1;
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15, min2 / 0.15,
max2 / 0.15);
this->cmd_ = buf;
this->cmd_ = str_sprintf("detRangeCfg -1 %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15);
} else if (min4 < 0 || max4 < 0) {
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
@@ -150,10 +145,9 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
this->max3_ = max3 = round(max3 / 0.15) * 0.15;
this->min4_ = min4 = this->max4_ = max4 = -1;
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15, min2 / 0.15,
max2 / 0.15, min3 / 0.15, max3 / 0.15);
this->cmd_ = buf;
this->cmd_ = str_sprintf("detRangeCfg -1 "
"%.0f %.0f %.0f %.0f %.0f %.0f",
min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15);
} else {
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
@@ -164,10 +158,10 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
this->min4_ = min4 = round(min4 / 0.15) * 0.15;
this->max4_ = max4 = round(max4 / 0.15) * 0.15;
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15,
min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15, min4 / 0.15, max4 / 0.15);
this->cmd_ = buf;
this->cmd_ = str_sprintf("detRangeCfg -1 "
"%.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f",
min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15, min4 / 0.15,
max4 / 0.15);
}
this->min1_ = min1;
@@ -209,10 +203,7 @@ SetLatencyCommand::SetLatencyCommand(float delay_after_detection, float delay_af
delay_after_disappear = std::round(delay_after_disappear / 0.025f) * 0.025f;
this->delay_after_detection_ = clamp(delay_after_detection, 0.0f, 1638.375f);
this->delay_after_disappear_ = clamp(delay_after_disappear, 0.0f, 1638.375f);
// max 32: "setLatency "(11) + float(8) + " "(1) + float(8) + null, rounded to 32
char buf[32];
snprintf(buf, sizeof(buf), "setLatency %.03f %.03f", this->delay_after_detection_, this->delay_after_disappear_);
this->cmd_ = buf;
this->cmd_ = str_sprintf("setLatency %.03f %.03f", this->delay_after_detection_, this->delay_after_disappear_);
};
uint8_t SetLatencyCommand::on_message(std::string &message) {

View File

@@ -75,8 +75,8 @@ class SetLatencyCommand : public Command {
class SensorCfgStartCommand : public Command {
public:
SensorCfgStartCommand(bool startup_mode) : startup_mode_(startup_mode) {
char tmp_cmd[20]; // "sensorCfgStart " (15) + "0/1" (1) + null = 17
buf_append_printf(tmp_cmd, sizeof(tmp_cmd), 0, "sensorCfgStart %d", startup_mode);
char tmp_cmd[20] = {0};
sprintf(tmp_cmd, "sensorCfgStart %d", startup_mode);
cmd_ = std::string(tmp_cmd);
}
uint8_t on_message(std::string &message) override;
@@ -142,8 +142,8 @@ class SensitivityCommand : public Command {
SensitivityCommand(uint8_t sensitivity) : sensitivity_(sensitivity) {
if (sensitivity > 9)
sensitivity_ = sensitivity = 9;
char tmp_cmd[20]; // "setSensitivity " (15) + "0-9" (1) + null = 17
buf_append_printf(tmp_cmd, sizeof(tmp_cmd), 0, "setSensitivity %d", sensitivity);
char tmp_cmd[20] = {0};
sprintf(tmp_cmd, "setSensitivity %d", sensitivity);
cmd_ = std::string(tmp_cmd);
};
uint8_t on_message(std::string &message) override;

View File

@@ -46,7 +46,7 @@ class ESPBTUUID {
esp_bt_uuid_t get_uuid() const;
std::string to_string() const; // NOLINT
std::string to_string() const;
const char *to_str(std::span<char, UUID_STR_LEN> output) const;
protected:

View File

@@ -50,7 +50,7 @@ void BLEClientBase::loop() {
this->set_state(espbt::ClientState::INIT);
return;
}
if (this->state_ == espbt::ClientState::INIT) {
if (this->state() == espbt::ClientState::INIT) {
auto ret = esp_ble_gattc_app_register(this->app_id);
if (ret) {
ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
@@ -60,7 +60,7 @@ void BLEClientBase::loop() {
}
// If idle, we can disable the loop as connect()
// will enable it again when a connection is needed.
else if (this->state_ == espbt::ClientState::IDLE) {
else if (this->state() == espbt::ClientState::IDLE) {
this->disable_loop();
}
}
@@ -86,7 +86,7 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
return false;
if (this->address_ == 0 || device.address_uint64() != this->address_)
return false;
if (this->state_ != espbt::ClientState::IDLE)
if (this->state() != espbt::ClientState::IDLE)
return false;
this->log_event_("Found device");
@@ -102,10 +102,10 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
void BLEClientBase::connect() {
// Prevent duplicate connection attempts
if (this->state_ == espbt::ClientState::CONNECTING || this->state_ == espbt::ClientState::CONNECTED ||
this->state_ == espbt::ClientState::ESTABLISHED) {
if (this->state() == espbt::ClientState::CONNECTING || this->state() == espbt::ClientState::CONNECTED ||
this->state() == espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%d] [%s] Connection already in progress, state=%s", this->connection_index_, this->address_str_,
espbt::client_state_to_string(this->state_));
espbt::client_state_to_string(this->state()));
return;
}
ESP_LOGI(TAG, "[%d] [%s] 0x%02x Connecting", this->connection_index_, this->address_str_, this->remote_addr_type_);
@@ -133,12 +133,12 @@ void BLEClientBase::connect() {
esp_err_t BLEClientBase::pair() { return esp_ble_set_encryption(this->remote_bda_, ESP_BLE_SEC_ENCRYPT); }
void BLEClientBase::disconnect() {
if (this->state_ == espbt::ClientState::IDLE || this->state_ == espbt::ClientState::DISCONNECTING) {
if (this->state() == espbt::ClientState::IDLE || this->state() == espbt::ClientState::DISCONNECTING) {
ESP_LOGI(TAG, "[%d] [%s] Disconnect requested, but already %s", this->connection_index_, this->address_str_,
espbt::client_state_to_string(this->state_));
espbt::client_state_to_string(this->state()));
return;
}
if (this->state_ == espbt::ClientState::CONNECTING || this->conn_id_ == UNSET_CONN_ID) {
if (this->state() == espbt::ClientState::CONNECTING || this->conn_id_ == UNSET_CONN_ID) {
ESP_LOGD(TAG, "[%d] [%s] Disconnect before connected, disconnect scheduled", this->connection_index_,
this->address_str_);
this->want_disconnect_ = true;
@@ -150,7 +150,7 @@ void BLEClientBase::disconnect() {
void BLEClientBase::unconditional_disconnect() {
// Disconnect without checking the state.
ESP_LOGI(TAG, "[%d] [%s] Disconnecting (conn_id: %d).", this->connection_index_, this->address_str_, this->conn_id_);
if (this->state_ == espbt::ClientState::DISCONNECTING) {
if (this->state() == espbt::ClientState::DISCONNECTING) {
this->log_error_("Already disconnecting");
return;
}
@@ -170,7 +170,7 @@ void BLEClientBase::unconditional_disconnect() {
this->log_gattc_warning_("esp_ble_gattc_close", err);
}
if (this->state_ == espbt::ClientState::DISCOVERED) {
if (this->state() == espbt::ClientState::DISCOVERED) {
this->set_address(0);
this->set_state(espbt::ClientState::IDLE);
} else {
@@ -295,18 +295,18 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
// ESP-IDF's BLE stack may send ESP_GATTC_OPEN_EVT after esp_ble_gattc_open() returns an
// error, if the error occurred at the BTA/GATT layer. This can result in the event
// arriving after we've already transitioned to IDLE state.
if (this->state_ == espbt::ClientState::IDLE) {
if (this->state() == espbt::ClientState::IDLE) {
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in IDLE state (status=%d), ignoring", this->connection_index_,
this->address_str_, param->open.status);
break;
}
if (this->state_ != espbt::ClientState::CONNECTING) {
if (this->state() != espbt::ClientState::CONNECTING) {
// This should not happen but lets log it in case it does
// because it means we have a bad assumption about how the
// ESP BT stack works.
ESP_LOGE(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in %s state (status=%d)", this->connection_index_,
this->address_str_, espbt::client_state_to_string(this->state_), param->open.status);
this->address_str_, espbt::client_state_to_string(this->state()), param->open.status);
}
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
this->log_gattc_warning_("Connection open", param->open.status);
@@ -327,7 +327,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
// Cached connections already connected with medium parameters, no update needed
// only set our state, subclients might have more stuff to do yet.
this->state_ = espbt::ClientState::ESTABLISHED;
this->set_state_internal_(espbt::ClientState::ESTABLISHED);
break;
}
// For V3_WITHOUT_CACHE, we already set fast params before connecting
@@ -356,7 +356,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
return false;
// Check if we were disconnected while waiting for service discovery
if (param->disconnect.reason == ESP_GATT_CONN_TERMINATE_PEER_USER &&
this->state_ == espbt::ClientState::CONNECTED) {
this->state() == espbt::ClientState::CONNECTED) {
this->log_warning_("Remote closed during discovery");
} else {
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason 0x%02x", this->connection_index_, this->address_str_,
@@ -433,7 +433,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
#endif
}
ESP_LOGI(TAG, "[%d] [%s] Service discovery complete", this->connection_index_, this->address_str_);
this->state_ = espbt::ClientState::ESTABLISHED;
this->set_state_internal_(espbt::ClientState::ESTABLISHED);
break;
}
case ESP_GATTC_READ_DESCR_EVT: {

View File

@@ -44,7 +44,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
void unconditional_disconnect();
void release_services();
bool connected() { return this->state_ == espbt::ClientState::ESTABLISHED; }
bool connected() { return this->state() == espbt::ClientState::ESTABLISHED; }
void set_auto_connect(bool auto_connect) { this->auto_connect_ = auto_connect; }

View File

@@ -105,15 +105,13 @@ void ESP32BLETracker::loop() {
}
// Check for scan timeout - moved here from scheduler to avoid false reboots
// when the loop is blocked
// when the loop is blocked. This must run every iteration for safety.
if (this->scanner_state_ == ScannerState::RUNNING) {
switch (this->scan_timeout_state_) {
case ScanTimeoutState::MONITORING: {
uint32_t now = App.get_loop_component_start_time();
uint32_t timeout_ms = this->scan_duration_ * 2000;
// Robust time comparison that handles rollover correctly
// This works because unsigned arithmetic wraps around predictably
if ((now - this->scan_start_time_) > timeout_ms) {
if ((App.get_loop_component_start_time() - this->scan_start_time_) > this->scan_timeout_ms_) {
// First time we've seen the timeout exceeded - wait one more loop iteration
// This ensures all components have had a chance to process pending events
// This is because esp32_ble may not have run yet and called
@@ -128,13 +126,31 @@ void ESP32BLETracker::loop() {
ESP_LOGE(TAG, "Scan never terminated, rebooting");
App.reboot();
break;
case ScanTimeoutState::INACTIVE:
// This case should be unreachable - scanner and timeout states are always synchronized
break;
}
}
// Fast path: skip expensive client state counting and processing
// if no state has changed since last loop iteration.
//
// How state changes ensure we reach the code below:
// - handle_scanner_failure_(): scanner_state_ becomes FAILED via set_scanner_state_(), or
// scan_set_param_failed_ requires scanner_state_==RUNNING which can only be reached via
// set_scanner_state_(RUNNING) in gap_scan_start_complete_() (scan params are set during
// STARTING, not RUNNING, so version is always incremented before this condition is true)
// - start_scan_(): scanner_state_ becomes IDLE via set_scanner_state_() in cleanup_scan_state_()
// - try_promote_discovered_clients_(): client enters DISCOVERED via set_state(), or
// connecting client finishes (state change), or scanner reaches RUNNING/IDLE
//
// All conditions that affect the logic below are tied to state changes that increment
// state_version_, so the fast path is safe.
if (this->state_version_ == this->last_processed_version_) {
return;
}
this->last_processed_version_ = this->state_version_;
// State changed - do full processing
ClientStateCounts counts = this->count_client_states_();
if (counts != this->client_state_counts_) {
this->client_state_counts_ = counts;
@@ -142,6 +158,7 @@ void ESP32BLETracker::loop() {
this->client_state_counts_.discovered, this->client_state_counts_.disconnecting);
}
// Scanner failure: reached when set_scanner_state_(FAILED) or scan_set_param_failed_ set
if (this->scanner_state_ == ScannerState::FAILED ||
(this->scan_set_param_failed_ && this->scanner_state_ == ScannerState::RUNNING)) {
this->handle_scanner_failure_();
@@ -160,6 +177,8 @@ void ESP32BLETracker::loop() {
*/
// Start scan: reached when scanner_state_ becomes IDLE (via set_scanner_state_()) and
// all clients are idle (their state changes increment version when they finish)
if (this->scanner_state_ == ScannerState::IDLE && !counts.connecting && !counts.disconnecting && !counts.discovered) {
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
this->update_coex_preference_(false);
@@ -168,8 +187,9 @@ void ESP32BLETracker::loop() {
this->start_scan_(false); // first = false
}
}
// If there is a discovered client and no connecting
// clients, then promote the discovered client to ready to connect.
// Promote discovered clients: reached when a client's state becomes DISCOVERED (via set_state()),
// or when a blocking condition clears (connecting client finishes, scanner reaches RUNNING/IDLE).
// All these trigger state_version_ increment, so we'll process and check promotion eligibility.
// We check both RUNNING and IDLE states because:
// - RUNNING: gap_scan_event_handler initiates stop_scan_() but promotion can happen immediately
// - IDLE: Scanner has already stopped (naturally or by gap_scan_event_handler)
@@ -236,6 +256,7 @@ void ESP32BLETracker::start_scan_(bool first) {
// Start timeout monitoring in loop() instead of using scheduler
// This prevents false reboots when the loop is blocked
this->scan_start_time_ = App.get_loop_component_start_time();
this->scan_timeout_ms_ = this->scan_duration_ * 2000;
this->scan_timeout_state_ = ScanTimeoutState::MONITORING;
esp_err_t err = esp_ble_gap_set_scan_params(&this->scan_params_);
@@ -253,6 +274,10 @@ void ESP32BLETracker::start_scan_(bool first) {
void ESP32BLETracker::register_client(ESPBTClient *client) {
#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
client->app_id = ++this->app_id_;
// Give client a pointer to our state_version_ so it can notify us of state changes.
// This enables loop() fast-path optimization - we skip expensive work when no state changed.
// Safe because ESP32BLETracker (singleton) outlives all registered clients.
client->set_tracker_state_version(&this->state_version_);
this->clients_.push_back(client);
this->recalculate_advertisement_parser_types();
#endif
@@ -382,6 +407,7 @@ void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
void ESP32BLETracker::set_scanner_state_(ScannerState state) {
this->scanner_state_ = state;
this->state_version_++;
for (auto *listener : this->scanner_state_listeners_) {
listener->on_scanner_state(state);
}

View File

@@ -216,6 +216,19 @@ enum class ConnectionType : uint8_t {
V3_WITHOUT_CACHE
};
/// Base class for BLE GATT clients that connect to remote devices.
///
/// State Change Tracking Design:
/// -----------------------------
/// ESP32BLETracker::loop() needs to know when client states change to avoid
/// expensive polling. Rather than checking all clients every iteration (~7000/min),
/// we use a version counter owned by ESP32BLETracker that clients increment on
/// state changes. The tracker compares versions to skip work when nothing changed.
///
/// Ownership: ESP32BLETracker owns state_version_. Clients hold a non-owning
/// pointer (tracker_state_version_) set during register_client(). Clients
/// increment the counter through this pointer when their state changes.
/// The pointer may be null if the client is not registered with a tracker.
class ESPBTClient : public ESPBTDeviceListener {
public:
virtual bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
@@ -225,26 +238,49 @@ class ESPBTClient : public ESPBTDeviceListener {
virtual void disconnect() = 0;
bool disconnect_pending() const { return this->want_disconnect_; }
void cancel_pending_disconnect() { this->want_disconnect_ = false; }
/// Set the client state with IDLE handling (clears want_disconnect_).
/// Notifies the tracker of state change for loop optimization.
virtual void set_state(ClientState st) {
this->state_ = st;
this->set_state_internal_(st);
if (st == ClientState::IDLE) {
this->want_disconnect_ = false;
}
}
ClientState state() const { return state_; }
ClientState state() const { return this->state_; }
/// Called by ESP32BLETracker::register_client() to enable state change notifications.
/// The pointer must remain valid for the lifetime of the client (guaranteed since
/// ESP32BLETracker is a singleton that outlives all clients).
void set_tracker_state_version(uint8_t *version) { this->tracker_state_version_ = version; }
// Memory optimized layout
uint8_t app_id; // App IDs are small integers assigned sequentially
protected:
// Group 1: 1-byte types
ClientState state_{ClientState::INIT};
/// Set state without IDLE handling - use for direct state transitions.
/// Increments the tracker's state version counter to signal that loop()
/// should do full processing on the next iteration.
void set_state_internal_(ClientState st) {
this->state_ = st;
// Notify tracker that state changed (tracker_state_version_ is owned by ESP32BLETracker)
if (this->tracker_state_version_ != nullptr) {
(*this->tracker_state_version_)++;
}
}
// want_disconnect_ is set to true when a disconnect is requested
// while the client is connecting. This is used to disconnect the
// client as soon as we get the connection id (conn_id_) from the
// ESP_GATTC_OPEN_EVT event.
bool want_disconnect_{false};
// 2 bytes used, 2 bytes padding
private:
ClientState state_{ClientState::INIT};
/// Non-owning pointer to ESP32BLETracker::state_version_. When this client's
/// state changes, we increment the tracker's counter to signal that loop()
/// should perform full processing. Null if client not registered with tracker.
uint8_t *tracker_state_version_{nullptr};
};
class ESP32BLETracker : public Component,
@@ -380,6 +416,16 @@ class ESP32BLETracker : public Component,
// Group 4: 1-byte types (enums, uint8_t, bool)
uint8_t app_id_{0};
uint8_t scan_start_fail_count_{0};
/// Version counter for loop() fast-path optimization. Incremented when:
/// - Scanner state changes (via set_scanner_state_())
/// - Any registered client's state changes (clients hold pointer to this counter)
/// Owned by this class; clients receive non-owning pointer via register_client().
/// When loop() sees state_version_ == last_processed_version_, it skips expensive
/// client state counting and takes the fast path (just timeout check + return).
uint8_t state_version_{0};
/// Last state_version_ value when loop() did full processing. Compared against
/// state_version_ to detect if any state changed since last iteration.
uint8_t last_processed_version_{0};
ScannerState scanner_state_{ScannerState::IDLE};
bool scan_continuous_;
bool scan_active_;
@@ -396,6 +442,8 @@ class ESP32BLETracker : public Component,
EXCEEDED_WAIT, // Timeout exceeded, waiting one loop before reboot
};
uint32_t scan_start_time_{0};
/// Precomputed timeout value: scan_duration_ * 2000
uint32_t scan_timeout_ms_{0};
ScanTimeoutState scan_timeout_state_{ScanTimeoutState::INACTIVE};
};

View File

@@ -69,10 +69,7 @@ void Esp32HostedUpdate::setup() {
// Get coprocessor version
esp_hosted_coprocessor_fwver_t ver_info;
if (esp_hosted_get_coprocessor_fwversion(&ver_info) == ESP_OK) {
// 16 bytes: "255.255.255" (11 chars) + null + safety margin
char buf[16];
snprintf(buf, sizeof(buf), "%d.%d.%d", ver_info.major1, ver_info.minor1, ver_info.patch1);
this->update_info_.current_version = buf;
this->update_info_.current_version = str_sprintf("%d.%d.%d", ver_info.major1, ver_info.minor1, ver_info.patch1);
} else {
this->update_info_.current_version = "unknown";
}

View File

@@ -6,11 +6,7 @@
#include "esphome/core/helpers.h"
#include "preferences.h"
#include <Arduino.h>
#include <core_esp8266_features.h>
extern "C" {
#include <user_interface.h>
}
#include <Esp.h>
namespace esphome {
@@ -20,19 +16,23 @@ void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); }
uint32_t IRAM_ATTR HOT micros() { return ::micros(); }
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
void arch_restart() {
system_restart();
ESP.restart(); // NOLINT(readability-static-accessed-through-instance)
// restart() doesn't always end execution
while (true) { // NOLINT(clang-diagnostic-unreachable-code)
yield();
}
}
void arch_init() {}
void IRAM_ATTR HOT arch_feed_wdt() { system_soft_wdt_feed(); }
void IRAM_ATTR HOT arch_feed_wdt() {
ESP.wdtFeed(); // NOLINT(readability-static-accessed-through-instance)
}
uint8_t progmem_read_byte(const uint8_t *addr) {
return pgm_read_byte(addr); // NOLINT
}
uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() { return esp_get_cycle_count(); }
uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() {
return ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance)
}
uint32_t arch_get_cpu_freq_hz() { return F_CPU; }
void force_link_symbols() {

View File

@@ -99,7 +99,7 @@ void ESP8266GPIOPin::pin_mode(gpio::Flags flags) {
}
size_t ESP8266GPIOPin::dump_summary(char *buffer, size_t len) const {
return buf_append_printf(buffer, len, 0, "GPIO%u", this->pin_);
return snprintf(buffer, len, "GPIO%u", this->pin_);
}
bool ESP8266GPIOPin::digital_read() {

View File

@@ -90,7 +90,9 @@ async def setup_event_core_(var, config, *, event_types: list[str]):
for conf in config.get(CONF_ON_EVENT, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.StringRef, "event_type")], conf)
await automation.build_automation(
trigger, [(cg.std_string, "event_type")], conf
)
cg.add(var.set_event_types(event_types))

View File

@@ -14,10 +14,10 @@ template<typename... Ts> class TriggerEventAction : public Action<Ts...>, public
void play(const Ts &...x) override { this->parent_->trigger(this->event_type_.value(x...)); }
};
class EventTrigger : public Trigger<StringRef> {
class EventTrigger : public Trigger<std::string> {
public:
EventTrigger(Event *event) {
event->add_on_event_callback([this](StringRef event_type) { this->trigger(event_type); });
event->add_on_event_callback([this](const std::string &event_type) { this->trigger(event_type); });
}
};

View File

@@ -23,7 +23,7 @@ void Event::trigger(const std::string &event_type) {
}
this->last_event_type_ = found;
ESP_LOGD(TAG, "'%s' >> '%s'", this->get_name().c_str(), this->last_event_type_);
this->event_callback_.call(StringRef(found));
this->event_callback_.call(event_type);
#if defined(USE_EVENT) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_event(this);
#endif
@@ -45,7 +45,7 @@ void Event::set_event_types(const std::vector<const char *> &event_types) {
this->last_event_type_ = nullptr; // Reset when types change
}
void Event::add_on_event_callback(std::function<void(StringRef event_type)> &&callback) {
void Event::add_on_event_callback(std::function<void(const std::string &event_type)> &&callback) {
this->event_callback_.add(std::move(callback));
}

View File

@@ -70,10 +70,10 @@ class Event : public EntityBase, public EntityBase_DeviceClass {
/// Check if an event has been triggered.
bool has_event() const { return this->last_event_type_ != nullptr; }
void add_on_event_callback(std::function<void(StringRef event_type)> &&callback);
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);
protected:
LazyCallbackManager<void(StringRef event_type)> event_callback_;
LazyCallbackManager<void(const std::string &event_type)> event_callback_;
FixedVector<const char *> types_;
private:

View File

@@ -160,7 +160,7 @@ void EZOSensor::loop() {
this->commands_.pop_front();
}
void EZOSensor::add_command_(const char *command, EzoCommandType command_type, uint16_t delay_ms) {
void EZOSensor::add_command_(const std::string &command, EzoCommandType command_type, uint16_t delay_ms) {
std::unique_ptr<EzoCommand> ezo_command(new EzoCommand);
ezo_command->command = command;
ezo_command->command_type = command_type;
@@ -169,17 +169,13 @@ void EZOSensor::add_command_(const char *command, EzoCommandType command_type, u
}
void EZOSensor::set_calibration_point_(EzoCalibrationType type, float value) {
// max 21: "Cal,"(4) + type(4) + ","(1) + float(11) + null; use 24 for safety
char payload[24];
snprintf(payload, sizeof(payload), "Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value);
std::string payload = str_sprintf("Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value);
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
}
void EZOSensor::set_address(uint8_t address) {
if (address > 0 && address < 128) {
// max 8: "I2C,"(4) + uint8(3) + null
char payload[8];
snprintf(payload, sizeof(payload), "I2C,%u", address);
std::string payload = str_sprintf("I2C,%u", address);
this->new_address_ = address;
this->add_command_(payload, EzoCommandType::EZO_I2C);
} else {
@@ -198,9 +194,7 @@ void EZOSensor::get_slope() { this->add_command_("Slope,?", EzoCommandType::EZO_
void EZOSensor::get_t() { this->add_command_("T,?", EzoCommandType::EZO_T); }
void EZOSensor::set_t(float value) {
// max 14 bytes: "T,"(2) + float with "%0.2f" (up to 11 chars) + null(1); use 16 for alignment
char payload[16];
snprintf(payload, sizeof(payload), "T,%0.2f", value);
std::string payload = str_sprintf("T,%0.2f", value);
this->add_command_(payload, EzoCommandType::EZO_T);
}
@@ -221,9 +215,7 @@ void EZOSensor::set_calibration_point_high(float value) {
}
void EZOSensor::set_calibration_generic(float value) {
// exact 16 bytes: "Cal," (4) + float with "%0.2f" (up to 11 chars, e.g. "-9999999.99") + null (1) = 16
char payload[16];
snprintf(payload, sizeof(payload), "Cal,%0.2f", value);
std::string payload = str_sprintf("Cal,%0.2f", value);
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
}
@@ -231,11 +223,13 @@ void EZOSensor::clear_calibration() { this->add_command_("Cal,clear", EzoCommand
void EZOSensor::get_led_state() { this->add_command_("L,?", EzoCommandType::EZO_LED); }
void EZOSensor::set_led_state(bool on) { this->add_command_(on ? "L,1" : "L,0", EzoCommandType::EZO_LED); }
void EZOSensor::send_custom(const std::string &to_send) {
this->add_command_(to_send.c_str(), EzoCommandType::EZO_CUSTOM);
void EZOSensor::set_led_state(bool on) {
std::string to_send = "L,";
to_send += on ? "1" : "0";
this->add_command_(to_send, EzoCommandType::EZO_LED);
}
void EZOSensor::send_custom(const std::string &to_send) { this->add_command_(to_send, EzoCommandType::EZO_CUSTOM); }
} // namespace ezo
} // namespace esphome

View File

@@ -92,7 +92,7 @@ class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2
std::deque<std::unique_ptr<EzoCommand>> commands_;
int new_address_;
void add_command_(const char *command, EzoCommandType command_type, uint16_t delay_ms = 300);
void add_command_(const std::string &command, EzoCommandType command_type, uint16_t delay_ms = 300);
void set_calibration_point_(EzoCalibrationType type, float value);

View File

@@ -77,7 +77,7 @@ FanSpeedSetTrigger = fan_ns.class_(
"FanSpeedSetTrigger", automation.Trigger.template(cg.int_)
)
FanPresetSetTrigger = fan_ns.class_(
"FanPresetSetTrigger", automation.Trigger.template(cg.StringRef)
"FanPresetSetTrigger", automation.Trigger.template(cg.std_string)
)
FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template())
@@ -287,7 +287,7 @@ async def setup_fan_core_(var, config):
await automation.build_automation(trigger, [(cg.int_, "x")], conf)
for conf in config.get(CONF_ON_PRESET_SET, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.StringRef, "x")], conf)
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
async def register_fan(var, config):

View File

@@ -208,7 +208,7 @@ class FanSpeedSetTrigger : public Trigger<int> {
int last_speed_;
};
class FanPresetSetTrigger : public Trigger<StringRef> {
class FanPresetSetTrigger : public Trigger<std::string> {
public:
FanPresetSetTrigger(Fan *state) {
state->add_on_state_callback([this, state]() {
@@ -216,7 +216,7 @@ class FanPresetSetTrigger : public Trigger<StringRef> {
auto should_trigger = preset_mode != this->last_preset_mode_;
this->last_preset_mode_ = preset_mode;
if (should_trigger) {
this->trigger(preset_mode);
this->trigger(std::string(preset_mode));
}
});
this->last_preset_mode_ = state->get_preset_mode();

View File

@@ -163,10 +163,9 @@ bool GDK101Component::read_fw_version_(uint8_t *data) {
return false;
}
// max 8: "255.255" (7 chars) + null
char buf[8];
snprintf(buf, sizeof(buf), "%d.%d", data[0], data[1]);
this->fw_version_text_sensor_->publish_state(buf);
const std::string fw_version_str = str_sprintf("%d.%d", data[0], data[1]);
this->fw_version_text_sensor_->publish_state(fw_version_str);
}
#endif // USE_TEXT_SENSOR
return true;

View File

@@ -97,7 +97,7 @@ void HomeassistantNumber::control(float value) {
entity_value.key = VALUE_KEY;
// Stack buffer - no heap allocation; %g produces shortest representation
char value_buf[16];
buf_append_printf(value_buf, sizeof(value_buf), 0, "%g", value);
snprintf(value_buf, sizeof(value_buf), "%g", value);
entity_value.value = StringRef(value_buf);
api::global_api_server->send_homeassistant_action(resp);

View File

@@ -42,8 +42,8 @@ ErrorCode I2CDevice::read_register16(uint16_t a_register, uint8_t *data, size_t
}
ErrorCode I2CDevice::write_register(uint8_t a_register, const uint8_t *data, size_t len) const {
SmallBufferWithHeapFallback<17> buffer_alloc(len + 1); // Most I2C writes are <= 16 bytes
uint8_t *buffer = buffer_alloc.get();
SmallBufferWithHeapFallback<17> buffer_alloc; // Most I2C writes are <= 16 bytes
uint8_t *buffer = buffer_alloc.get(len + 1);
buffer[0] = a_register;
std::copy(data, data + len, buffer + 1);
@@ -51,8 +51,8 @@ ErrorCode I2CDevice::write_register(uint8_t a_register, const uint8_t *data, siz
}
ErrorCode I2CDevice::write_register16(uint16_t a_register, const uint8_t *data, size_t len) const {
SmallBufferWithHeapFallback<18> buffer_alloc(len + 2); // Most I2C writes are <= 16 bytes + 2 for register
uint8_t *buffer = buffer_alloc.get();
SmallBufferWithHeapFallback<18> buffer_alloc; // Most I2C writes are <= 16 bytes + 2 for register
uint8_t *buffer = buffer_alloc.get(len + 2);
buffer[0] = a_register >> 8;
buffer[1] = a_register;

View File

@@ -11,6 +11,22 @@
namespace esphome {
namespace i2c {
/// @brief Helper class for efficient buffer allocation - uses stack for small sizes, heap for large
template<size_t STACK_SIZE> class SmallBufferWithHeapFallback {
public:
uint8_t *get(size_t size) {
if (size <= STACK_SIZE) {
return this->stack_buffer_;
}
this->heap_buffer_ = std::unique_ptr<uint8_t[]>(new uint8_t[size]);
return this->heap_buffer_.get();
}
private:
uint8_t stack_buffer_[STACK_SIZE];
std::unique_ptr<uint8_t[]> heap_buffer_;
};
/// @brief Error codes returned by I2CBus and I2CDevice methods
enum ErrorCode {
NO_ERROR = 0, ///< No error found during execution of method
@@ -76,8 +92,8 @@ class I2CBus {
total_len += read_buffers[i].len;
}
SmallBufferWithHeapFallback<128> buffer_alloc(total_len); // Most I2C reads are small
uint8_t *buffer = buffer_alloc.get();
SmallBufferWithHeapFallback<128> buffer_alloc; // Most I2C reads are small
uint8_t *buffer = buffer_alloc.get(total_len);
auto err = this->write_readv(address, nullptr, 0, buffer, total_len);
if (err != ERROR_OK)
@@ -100,8 +116,8 @@ class I2CBus {
total_len += write_buffers[i].len;
}
SmallBufferWithHeapFallback<128> buffer_alloc(total_len); // Most I2C writes are small
uint8_t *buffer = buffer_alloc.get();
SmallBufferWithHeapFallback<128> buffer_alloc; // Most I2C writes are small
uint8_t *buffer = buffer_alloc.get(total_len);
size_t pos = 0;
for (size_t i = 0; i != count; i++) {

View File

@@ -162,7 +162,7 @@ void MAX6956GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this-
bool MAX6956GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
void MAX6956GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
size_t MAX6956GPIOPin::dump_summary(char *buffer, size_t len) const {
return buf_append_printf(buffer, len, 0, "%u via Max6956", this->pin_);
return snprintf(buffer, len, "%u via Max6956", this->pin_);
}
} // namespace max6956

View File

@@ -100,7 +100,7 @@ void MCP23016GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this
bool MCP23016GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
void MCP23016GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
size_t MCP23016GPIOPin::dump_summary(char *buffer, size_t len) const {
return buf_append_printf(buffer, len, 0, "%u via MCP23016", this->pin_);
return snprintf(buffer, len, "%u via MCP23016", this->pin_);
}
} // namespace mcp23016

View File

@@ -17,7 +17,7 @@ template<uint8_t N> void MCP23XXXGPIOPin<N>::digital_write(bool value) {
this->parent_->digital_write(this->pin_, value != this->inverted_);
}
template<uint8_t N> size_t MCP23XXXGPIOPin<N>::dump_summary(char *buffer, size_t len) const {
return buf_append_printf(buffer, len, 0, "%u via MCP23XXX", this->pin_);
return snprintf(buffer, len, "%u via MCP23XXX", this->pin_);
}
template class MCP23XXXGPIOPin<8>;

View File

@@ -271,31 +271,24 @@ class ServerRegister {
// Formats a raw value into a string representation based on the value type for debugging
std::string format_value(int64_t value) const {
// max 44: float with %.1f can be up to 42 chars (3.4e38 → 39 integer digits + sign + decimal + 1 digit)
// plus null terminator = 43, rounded to 44 for 4-byte alignment
char buf[44];
switch (this->value_type) {
case SensorValueType::U_WORD:
case SensorValueType::U_DWORD:
case SensorValueType::U_DWORD_R:
case SensorValueType::U_QWORD:
case SensorValueType::U_QWORD_R:
buf_append_printf(buf, sizeof(buf), 0, "%" PRIu64, static_cast<uint64_t>(value));
return buf;
return std::to_string(static_cast<uint64_t>(value));
case SensorValueType::S_WORD:
case SensorValueType::S_DWORD:
case SensorValueType::S_DWORD_R:
case SensorValueType::S_QWORD:
case SensorValueType::S_QWORD_R:
buf_append_printf(buf, sizeof(buf), 0, "%" PRId64, value);
return buf;
return std::to_string(value);
case SensorValueType::FP32_R:
case SensorValueType::FP32:
buf_append_printf(buf, sizeof(buf), 0, "%.1f", bit_cast<float>(static_cast<uint32_t>(value)));
return buf;
return str_sprintf("%.1f", bit_cast<float>(static_cast<uint32_t>(value)));
default:
buf_append_printf(buf, sizeof(buf), 0, "%" PRId64, value);
return buf;
return std::to_string(value);
}
}

View File

@@ -16,20 +16,12 @@ void ModbusTextSensor::parse_and_publish(const std::vector<uint8_t> &data) {
while ((items_left > 0) && index < data.size()) {
uint8_t b = data[index];
switch (this->encode_) {
case RawEncoding::HEXBYTES: {
// max 3: 2 hex digits + null
char hex_buf[3];
snprintf(hex_buf, sizeof(hex_buf), "%02x", b);
output_str += hex_buf;
case RawEncoding::HEXBYTES:
output_str += str_snprintf("%02x", 2, b);
break;
}
case RawEncoding::COMMA: {
// max 5: optional ','(1) + uint8(3) + null, for both ",%d" and "%d"
char dec_buf[5];
snprintf(dec_buf, sizeof(dec_buf), index != this->offset ? ",%d" : "%d", b);
output_str += dec_buf;
case RawEncoding::COMMA:
output_str += str_sprintf(index != this->offset ? ",%d" : "%d", b);
break;
}
case RawEncoding::ANSI:
if (b < 0x20)
break;

View File

@@ -154,7 +154,7 @@ void MPR121GPIOPin::digital_write(bool value) {
}
size_t MPR121GPIOPin::dump_summary(char *buffer, size_t len) const {
return buf_append_printf(buffer, len, 0, "ELE%u on MPR121", this->pin_);
return snprintf(buffer, len, "ELE%u on MPR121", this->pin_);
}
} // namespace mpr121

View File

@@ -635,8 +635,7 @@ void MQTTClientComponent::set_log_message_template(MQTTMessage &&message) { this
const MQTTDiscoveryInfo &MQTTClientComponent::get_discovery_info() const { return this->discovery_info_; }
void MQTTClientComponent::set_topic_prefix(const std::string &topic_prefix, const std::string &check_topic_prefix) {
if (App.is_name_add_mac_suffix_enabled() && (topic_prefix == check_topic_prefix)) {
char buf[ESPHOME_DEVICE_NAME_MAX_LEN + 1];
this->topic_prefix_ = str_sanitize_to(buf, App.get_name().c_str());
this->topic_prefix_ = str_sanitize(App.get_name());
} else {
this->topic_prefix_ = topic_prefix;
}

View File

@@ -48,8 +48,7 @@ void MQTTComponent::set_subscribe_qos(uint8_t qos) { this->subscribe_qos_ = qos;
void MQTTComponent::set_retain(bool retain) { this->retain_ = retain; }
std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discovery_info) const {
char sanitized_name[ESPHOME_DEVICE_NAME_MAX_LEN + 1];
str_sanitize_to(sanitized_name, App.get_name().c_str());
std::string sanitized_name = str_sanitize(App.get_name());
const char *comp_type = this->component_type();
char object_id_buf[OBJECT_ID_MAX_LEN];
StringRef object_id = this->get_default_object_id_to_(object_id_buf);
@@ -61,7 +60,7 @@ std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discove
p = append_char(p, '/');
p = append_str(p, comp_type, strlen(comp_type));
p = append_char(p, '/');
p = append_str(p, sanitized_name, strlen(sanitized_name));
p = append_str(p, sanitized_name.data(), sanitized_name.size());
p = append_char(p, '/');
p = append_str(p, object_id.c_str(), object_id.size());
p = append_str(p, "/config", 7);

View File

@@ -43,14 +43,6 @@ namespace network {
/// Buffer size for IP address string (IPv6 max: 39 chars + null)
static constexpr size_t IP_ADDRESS_BUFFER_SIZE = 40;
/// Lowercase hex digits in IP address string (A-F -> a-f for IPv6 per RFC 5952)
inline void lowercase_ip_str(char *buf) {
for (char *p = buf; *p; ++p) {
if (*p >= 'A' && *p <= 'F')
*p += 32;
}
}
struct IPAddress {
public:
#ifdef USE_HOST
@@ -60,15 +52,10 @@ struct IPAddress {
}
IPAddress(const std::string &in_address) { inet_aton(in_address.c_str(), &ip_addr_); }
IPAddress(const ip_addr_t *other_ip) { ip_addr_ = *other_ip; }
std::string str() const {
char buf[IP_ADDRESS_BUFFER_SIZE];
this->str_to(buf);
return buf;
}
std::string str() const { return str_lower_case(inet_ntoa(ip_addr_)); }
/// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes.
char *str_to(char *buf) const {
inet_ntop(AF_INET, &ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE);
return buf; // IPv4 only, no hex letters to lowercase
return const_cast<char *>(inet_ntop(AF_INET, &ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE));
}
#else
IPAddress() { ip_addr_set_zero(&ip_addr_); }
@@ -147,18 +134,9 @@ struct IPAddress {
bool is_ip4() const { return IP_IS_V4(&ip_addr_); }
bool is_ip6() const { return IP_IS_V6(&ip_addr_); }
bool is_multicast() const { return ip_addr_ismulticast(&ip_addr_); }
std::string str() const {
char buf[IP_ADDRESS_BUFFER_SIZE];
this->str_to(buf);
return buf;
}
std::string str() const { return str_lower_case(ipaddr_ntoa(&ip_addr_)); }
/// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes.
/// Output is lowercased per RFC 5952 (IPv6 hex digits a-f).
char *str_to(char *buf) const {
ipaddr_ntoa_r(&ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE);
lowercase_ip_str(buf);
return buf;
}
char *str_to(char *buf) const { return ipaddr_ntoa_r(&ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE); }
bool operator==(const IPAddress &other) const { return ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
bool operator!=(const IPAddress &other) const { return !ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
IPAddress &operator+=(uint8_t increase) {

View File

@@ -1,7 +1,6 @@
#include "nextion.h"
#include <cinttypes>
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/util.h"
@@ -1284,9 +1283,8 @@ void Nextion::check_pending_waveform_() {
size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size()
: 255; // ADDT command can only send 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);
std::string command = "addt " + to_string(component->get_component_id()) + "," +
to_string(component->get_wave_channel_id()) + "," + to_string(buffer_to_send);
if (!this->send_command_(command)) {
delete nb; // NOLINT(cppcoreguidelines-owning-memory)
this->waveform_queue_.pop_front();

View File

@@ -34,7 +34,7 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) {
}
char range_header[32];
buf_append_printf(range_header, sizeof(range_header), 0, "bytes=%" PRIu32 "-%" PRIu32, range_start, range_end);
sprintf(range_header, "bytes=%" PRIu32 "-%" PRIu32, range_start, range_end);
ESP_LOGV(TAG, "Range: %s", range_header);
http_client.addHeader("Range", range_header);
int code = http_client.GET();

View File

@@ -36,7 +36,7 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r
}
char range_header[32];
buf_append_printf(range_header, sizeof(range_header), 0, "bytes=%" PRIu32 "-%" PRIu32, range_start, range_end);
sprintf(range_header, "bytes=%" PRIu32 "-%" PRIu32, range_start, range_end);
ESP_LOGV(TAG, "Range: %s", range_header);
esp_http_client_set_header(http_client, "Range", range_header);
ESP_LOGV(TAG, "Open HTTP");

View File

@@ -181,7 +181,7 @@ void PCA6416AGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this
bool PCA6416AGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
void PCA6416AGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
size_t PCA6416AGPIOPin::dump_summary(char *buffer, size_t len) const {
return buf_append_printf(buffer, len, 0, "%u via PCA6416A", this->pin_);
return snprintf(buffer, len, "%u via PCA6416A", this->pin_);
}
} // namespace pca6416a

View File

@@ -130,7 +130,7 @@ void PCA9554GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this-
bool PCA9554GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
void PCA9554GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
size_t PCA9554GPIOPin::dump_summary(char *buffer, size_t len) const {
return buf_append_printf(buffer, len, 0, "%u via PCA9554", this->pin_);
return snprintf(buffer, len, "%u via PCA9554", this->pin_);
}
} // namespace pca9554

View File

@@ -107,7 +107,7 @@ void PCF8574GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this-
bool PCF8574GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
void PCF8574GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
size_t PCF8574GPIOPin::dump_summary(char *buffer, size_t len) const {
return buf_append_printf(buffer, len, 0, "%u via PCF8574", this->pin_);
return snprintf(buffer, len, "%u via PCF8574", this->pin_);
}
} // namespace pcf8574

View File

@@ -165,7 +165,7 @@ void PI4IOE5V6408GPIOPin::digital_write(bool value) {
this->parent_->digital_write(this->pin_, value != this->inverted_);
}
size_t PI4IOE5V6408GPIOPin::dump_summary(char *buffer, size_t len) const {
return buf_append_printf(buffer, len, 0, "%u via PI4IOE5V6408", this->pin_);
return snprintf(buffer, len, "%u via PI4IOE5V6408", this->pin_);
}
} // namespace pi4ioe5v6408

View File

@@ -8,8 +8,8 @@ namespace pipsolar {
static const char *const TAG = "pipsolar.output";
void PipsolarOutput::write_state(float state) {
char tmp[16];
snprintf(tmp, sizeof(tmp), this->set_command_, state);
char tmp[10];
sprintf(tmp, this->set_command_.c_str(), state);
if (std::find(this->possible_values_.begin(), this->possible_values_.end(), state) != this->possible_values_.end()) {
ESP_LOGD(TAG, "Will write: %s out of value %f / %02.0f", tmp, state, state);

View File

@@ -15,15 +15,13 @@ class PipsolarOutput : public output::FloatOutput {
public:
PipsolarOutput() {}
void set_parent(Pipsolar *parent) { this->parent_ = parent; }
void set_set_command(const char *command) { this->set_command_ = command; }
/// Prevent accidental use of std::string which would dangle
void set_set_command(const std::string &command) = delete;
void set_set_command(const std::string &command) { this->set_command_ = command; };
void set_possible_values(std::vector<float> possible_values) { this->possible_values_ = std::move(possible_values); }
void set_value(float value) { this->write_state(value); }
void set_value(float value) { this->write_state(value); };
protected:
void write_state(float state) override;
const char *set_command_{nullptr};
std::string set_command_;
Pipsolar *parent_;
std::vector<float> possible_values_;
};

View File

@@ -9,9 +9,14 @@ static const char *const TAG = "pipsolar.switch";
void PipsolarSwitch::dump_config() { LOG_SWITCH("", "Pipsolar Switch", this); }
void PipsolarSwitch::write_state(bool state) {
const char *command = state ? this->on_command_ : this->off_command_;
if (command != nullptr) {
this->parent_->queue_command(command);
if (state) {
if (!this->on_command_.empty()) {
this->parent_->queue_command(this->on_command_);
}
} else {
if (!this->off_command_.empty()) {
this->parent_->queue_command(this->off_command_);
}
}
}

View File

@@ -9,18 +9,15 @@ namespace pipsolar {
class Pipsolar;
class PipsolarSwitch : public switch_::Switch, public Component {
public:
void set_parent(Pipsolar *parent) { this->parent_ = parent; }
void set_on_command(const char *command) { this->on_command_ = command; }
void set_off_command(const char *command) { this->off_command_ = command; }
/// Prevent accidental use of std::string which would dangle
void set_on_command(const std::string &command) = delete;
void set_off_command(const std::string &command) = delete;
void set_parent(Pipsolar *parent) { this->parent_ = parent; };
void set_on_command(const std::string &command) { this->on_command_ = command; };
void set_off_command(const std::string &command) { this->off_command_ = command; };
void dump_config() override;
protected:
void write_state(bool state) override;
const char *on_command_{nullptr};
const char *off_command_{nullptr};
std::string on_command_;
std::string off_command_;
Pipsolar *parent_;
};

View File

@@ -1,5 +1,4 @@
#include "rc522_spi.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
// Based on:
@@ -71,7 +70,7 @@ void RC522Spi::pcd_read_register(PcdRegister reg, ///< The register to read fro
index++;
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
buf_append_printf(cstrb, sizeof(cstrb), 0, " %x", values[0]);
sprintf(cstrb, " %x", values[0]);
buf.append(cstrb);
#endif
}
@@ -79,7 +78,7 @@ void RC522Spi::pcd_read_register(PcdRegister reg, ///< The register to read fro
values[index] = transfer_byte(address); // Read value and tell that we want to read the same address again.
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
buf_append_printf(cstrb, sizeof(cstrb), 0, " %x", values[index]);
sprintf(cstrb, " %x", values[index]);
buf.append(cstrb);
#endif
@@ -89,7 +88,7 @@ void RC522Spi::pcd_read_register(PcdRegister reg, ///< The register to read fro
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
buf = buf + " ";
buf_append_printf(cstrb, sizeof(cstrb), 0, "%x", values[index]);
sprintf(cstrb, "%x", values[index]);
buf.append(cstrb);
ESP_LOGVV(TAG, "read_register_array_(%x, %d, , %d) -> %s", reg, count, rx_align, buf.c_str());
@@ -128,7 +127,7 @@ void RC522Spi::pcd_write_register(PcdRegister reg, ///< The register to write t
transfer_byte(values[index]);
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
buf_append_printf(cstrb, sizeof(cstrb), 0, " %x", values[index]);
sprintf(cstrb, " %x", values[index]);
buf.append(cstrb);
#endif
}

View File

@@ -33,7 +33,7 @@ SelectPtr = Select.operator("ptr")
# Triggers
SelectStateTrigger = select_ns.class_(
"SelectStateTrigger",
automation.Trigger.template(cg.StringRef, cg.size_t),
automation.Trigger.template(cg.std_string, cg.size_t),
)
# Actions
@@ -100,7 +100,7 @@ async def setup_select_core_(var, config, *, options: list[str]):
for conf in config.get(CONF_ON_VALUE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(cg.StringRef, "x"), (cg.size_t, "i")], conf
trigger, [(cg.std_string, "x"), (cg.size_t, "i")], conf
)
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:

View File

@@ -6,11 +6,11 @@
namespace esphome::select {
class SelectStateTrigger : public Trigger<StringRef, size_t> {
class SelectStateTrigger : public Trigger<std::string, size_t> {
public:
explicit SelectStateTrigger(Select *parent) : parent_(parent) {
parent->add_on_state_callback(
[this](size_t index) { this->trigger(StringRef(this->parent_->option_at(index)), index); });
[this](size_t index) { this->trigger(std::string(this->parent_->option_at(index)), index); });
}
protected:

View File

@@ -52,7 +52,7 @@ void SHTCXComponent::dump_config() {
ESP_LOGCONFIG(TAG,
"SHTCx:\n"
" Model: %s (%04x)",
to_string(this->type_), this->sensor_id_); // NOLINT
to_string(this->type_), this->sensor_id_);
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);

View File

@@ -1,5 +1,4 @@
#include "sim800l.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <cstring>
@@ -51,8 +50,8 @@ void Sim800LComponent::update() {
} else if (state_ == STATE_RECEIVED_SMS) {
// Serial Buffer should have flushed.
// Send cmd to delete received sms
char delete_cmd[20]; // "AT+CMGD=" (8) + uint8_t (max 3) + null = 12 <= 20
buf_append_printf(delete_cmd, sizeof(delete_cmd), 0, "AT+CMGD=%d", this->parse_index_);
char delete_cmd[20];
sprintf(delete_cmd, "AT+CMGD=%d", this->parse_index_);
this->send_cmd_(delete_cmd);
this->state_ = STATE_CHECK_SMS;
this->expect_ack_ = true;

View File

@@ -104,10 +104,7 @@ std::vector<ObisInfo> SmlFile::get_obis_info() {
std::string bytes_repr(const BytesView &buffer) {
std::string repr;
for (auto const value : buffer) {
// max 3: 2 hex digits + null
char hex_buf[3];
snprintf(hex_buf, sizeof(hex_buf), "%02x", static_cast<unsigned int>(value));
repr += hex_buf;
repr += str_sprintf("%02x", value & 0xff);
}
return repr;
}
@@ -149,11 +146,7 @@ ObisInfo::ObisInfo(const BytesView &server_id, const SmlNode &val_list_entry) :
}
std::string ObisInfo::code_repr() const {
// max 20: "255-255:255.255.255" (19 chars) + null
char buf[20];
snprintf(buf, sizeof(buf), "%d-%d:%d.%d.%d", this->code[0], this->code[1], this->code[2], this->code[3],
this->code[4]);
return buf;
return str_sprintf("%d-%d:%d.%d.%d", this->code[0], this->code[1], this->code[2], this->code[3], this->code[4]);
}
} // namespace sml

View File

@@ -65,7 +65,7 @@ float SN74HC165Component::get_setup_priority() const { return setup_priority::IO
bool SN74HC165GPIOPin::digital_read() { return this->parent_->digital_read_(this->pin_) != this->inverted_; }
size_t SN74HC165GPIOPin::dump_summary(char *buffer, size_t len) const {
return buf_append_printf(buffer, len, 0, "%u via SN74HC165", this->pin_);
return snprintf(buffer, len, "%u via SN74HC165", this->pin_);
}
} // namespace sn74hc165

View File

@@ -94,7 +94,7 @@ void SN74HC595GPIOPin::digital_write(bool value) {
this->parent_->digital_write_(this->pin_, value != this->inverted_);
}
size_t SN74HC595GPIOPin::dump_summary(char *buffer, size_t len) const {
return buf_append_printf(buffer, len, 0, "%u via SN74HC595", this->pin_);
return snprintf(buffer, len, "%u via SN74HC595", this->pin_);
}
} // namespace sn74hc595

View File

@@ -107,9 +107,9 @@ std::unique_ptr<Socket> socket_ip_loop_monitored(int type, int protocol) {
#endif /* USE_NETWORK_IPV6 */
}
socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_address, uint16_t port) {
socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const std::string &ip_address, uint16_t port) {
#if USE_NETWORK_IPV6
if (strchr(ip_address, ':') != nullptr) {
if (ip_address.find(':') != std::string::npos) {
if (addrlen < sizeof(sockaddr_in6)) {
errno = EINVAL;
return 0;
@@ -121,14 +121,14 @@ socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_
#ifdef USE_SOCKET_IMPL_BSD_SOCKETS
// Use standard inet_pton for BSD sockets
if (inet_pton(AF_INET6, ip_address, &server->sin6_addr) != 1) {
if (inet_pton(AF_INET6, ip_address.c_str(), &server->sin6_addr) != 1) {
errno = EINVAL;
return 0;
}
#else
// Use LWIP-specific functions
ip6_addr_t ip6;
inet6_aton(ip_address, &ip6);
inet6_aton(ip_address.c_str(), &ip6);
memcpy(server->sin6_addr.un.u32_addr, ip6.addr, sizeof(ip6.addr));
#endif
return sizeof(sockaddr_in6);
@@ -141,7 +141,7 @@ socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_
auto *server = reinterpret_cast<sockaddr_in *>(addr);
memset(server, 0, sizeof(sockaddr_in));
server->sin_family = AF_INET;
server->sin_addr.s_addr = inet_addr(ip_address);
server->sin_addr.s_addr = inet_addr(ip_address.c_str());
server->sin_port = htons(port);
return sizeof(sockaddr_in);
}

View File

@@ -87,17 +87,7 @@ std::unique_ptr<Socket> socket_loop_monitored(int domain, int type, int protocol
std::unique_ptr<Socket> socket_ip_loop_monitored(int type, int protocol);
/// Set a sockaddr to the specified address and port for the IP version used by socket_ip().
/// @param addr Destination sockaddr structure
/// @param addrlen Size of the addr buffer
/// @param ip_address Null-terminated IP address string (IPv4 or IPv6)
/// @param port Port number in host byte order
/// @return Size of the sockaddr structure used, or 0 on error
socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_address, uint16_t port);
/// Convenience overload for std::string (backward compatible).
inline socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const std::string &ip_address, uint16_t port) {
return set_sockaddr(addr, addrlen, ip_address.c_str(), port);
}
socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const std::string &ip_address, uint16_t port);
/// Set a sockaddr to the any address and specified port for the IP version used by socket_ip().
socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port);

View File

@@ -7,14 +7,14 @@ DEPENDENCIES = ["network"]
status_ns = cg.esphome_ns.namespace("status")
StatusBinarySensor = status_ns.class_(
"StatusBinarySensor", binary_sensor.BinarySensor, cg.PollingComponent
"StatusBinarySensor", binary_sensor.BinarySensor, cg.Component
)
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(
StatusBinarySensor,
device_class=DEVICE_CLASS_CONNECTIVITY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
).extend(cv.polling_component_schema("1s"))
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):

View File

@@ -10,11 +10,12 @@
#include "esphome/components/api/api_server.h"
#endif
namespace esphome::status {
namespace esphome {
namespace status {
static const char *const TAG = "status";
void StatusBinarySensor::update() {
void StatusBinarySensor::loop() {
bool status = network::is_connected();
#ifdef USE_MQTT
if (mqtt::global_mqtt_client != nullptr) {
@@ -32,4 +33,5 @@ void StatusBinarySensor::update() {
void StatusBinarySensor::setup() { this->publish_initial_state(false); }
void StatusBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Status Binary Sensor", this); }
} // namespace esphome::status
} // namespace status
} // namespace esphome

View File

@@ -3,11 +3,12 @@
#include "esphome/core/component.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome::status {
namespace esphome {
namespace status {
class StatusBinarySensor : public binary_sensor::BinarySensor, public PollingComponent {
class StatusBinarySensor : public binary_sensor::BinarySensor, public Component {
public:
void update() override;
void loop() override;
void setup() override;
void dump_config() override;
@@ -15,4 +16,5 @@ class StatusBinarySensor : public binary_sensor::BinarySensor, public PollingCom
bool is_status_binary_sensor() const override { return true; }
};
} // namespace esphome::status
} // namespace status
} // namespace esphome

View File

@@ -14,9 +14,7 @@ class SunTextSensor : public text_sensor::TextSensor, public PollingComponent {
void set_parent(Sun *parent) { parent_ = parent; }
void set_elevation(double elevation) { elevation_ = elevation; }
void set_sunrise(bool sunrise) { sunrise_ = sunrise; }
void set_format(const char *format) { this->format_ = format; }
/// Prevent accidental use of std::string which would dangle
void set_format(const std::string &format) = delete;
void set_format(const std::string &format) { format_ = format; }
void update() override {
optional<ESPTime> res;
@@ -31,14 +29,14 @@ class SunTextSensor : public text_sensor::TextSensor, public PollingComponent {
}
char buf[ESPTime::STRFTIME_BUFFER_SIZE];
size_t len = res->strftime_to(buf, this->format_);
size_t len = res->strftime_to(buf, this->format_.c_str());
this->publish_state(buf, len);
}
void dump_config() override;
protected:
const char *format_{nullptr};
std::string format_{};
Sun *parent_;
double elevation_;
bool sunrise_;

View File

@@ -13,7 +13,7 @@ void SX1509GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->
bool SX1509GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
void SX1509GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
size_t SX1509GPIOPin::dump_summary(char *buffer, size_t len) const {
return buf_append_printf(buffer, len, 0, "%u via sx1509", this->pin_);
return snprintf(buffer, len, "%u via sx1509", this->pin_);
}
} // namespace sx1509

View File

@@ -47,27 +47,29 @@ void Syslog::log_(const int level, const char *tag, const char *message, size_t
size_t remaining = sizeof(packet);
// Write PRI - abort if this fails as packet would be malformed
offset = buf_append_printf(packet, sizeof(packet), 0, "<%d>", pri);
if (offset == 0) {
return; // PRI always produces at least "<0>" (3 chars), so 0 means error
int ret = snprintf(packet, remaining, "<%d>", pri);
if (ret <= 0 || static_cast<size_t>(ret) >= remaining) {
return;
}
remaining -= offset;
offset = ret;
remaining -= ret;
// Write timestamp directly into packet (RFC 5424: use "-" if time not valid or strftime fails)
auto now = this->time_->now();
size_t ts_written = now.is_valid() ? now.strftime(packet + offset, remaining, "%b %e %H:%M:%S") : 0;
if (ts_written > 0) {
offset += ts_written;
remaining -= ts_written;
} else if (remaining > 0) {
packet[offset++] = '-';
remaining--;
}
// Write hostname, tag, and message
offset = buf_append_printf(packet, sizeof(packet), offset, " %s %s: %.*s", App.get_name().c_str(), tag, (int) len,
message);
// Clamp to exclude null terminator position if buffer was filled
if (offset >= sizeof(packet)) {
offset = sizeof(packet) - 1;
ret = snprintf(packet + offset, remaining, " %s %s: %.*s", App.get_name().c_str(), tag, (int) len, message);
if (ret > 0) {
// snprintf returns chars that would be written; clamp to actual buffer space
offset += std::min(static_cast<size_t>(ret), remaining > 0 ? remaining - 1 : 0);
}
if (offset > 0) {

View File

@@ -139,7 +139,7 @@ void TCA9555GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this-
bool TCA9555GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
void TCA9555GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
size_t TCA9555GPIOPin::dump_summary(char *buffer, size_t len) const {
return buf_append_printf(buffer, len, 0, "%u via TCA9555", this->pin_);
return snprintf(buffer, len, "%u via TCA9555", this->pin_);
}
} // namespace tca9555

View File

@@ -118,7 +118,8 @@ async def to_code(config):
var = await alarm_control_panel.new_alarm_control_panel(config)
await cg.register_component(var, config)
if CONF_CODES in config:
cg.add(var.set_codes(config[CONF_CODES]))
for acode in config[CONF_CODES]:
cg.add(var.add_code(acode))
if CONF_REQUIRES_CODE_TO_ARM in config:
cg.add(var.set_requires_code_to_arm(config[CONF_REQUIRES_CODE_TO_ARM]))

View File

@@ -206,13 +206,7 @@ bool TemplateAlarmControlPanel::is_code_valid_(optional<std::string> code) {
if (!this->codes_.empty()) {
if (code.has_value()) {
ESP_LOGVV(TAG, "Checking code: %s", code.value().c_str());
// Use strcmp for const char* comparison
const char *code_cstr = code.value().c_str();
for (const char *stored_code : this->codes_) {
if (strcmp(stored_code, code_cstr) == 0)
return true;
}
return false;
return (std::count(this->codes_.begin(), this->codes_.end(), code.value()) == 1);
}
ESP_LOGD(TAG, "No code provided");
return false;

View File

@@ -1,7 +1,6 @@
#pragma once
#include <cinttypes>
#include <cstring>
#include <vector>
#include "esphome/core/automation.h"
@@ -87,14 +86,11 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl
AlarmSensorType type = ALARM_SENSOR_TYPE_DELAYED);
#endif
/** Set the codes (from initializer list).
/** add a code
*
* @param codes The list of valid codes
* @param code The code
*/
void set_codes(std::initializer_list<const char *> codes) { this->codes_ = codes; }
// Deleted overload to catch incorrect std::string usage at compile time
void set_codes(std::initializer_list<std::string> codes) = delete;
void add_code(const std::string &code) { this->codes_.push_back(code); }
/** set requires a code to arm
*
@@ -159,8 +155,8 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl
uint32_t pending_time_;
// the time in trigger
uint32_t trigger_time_;
// a list of codes (const char* pointers to string literals in flash)
FixedVector<const char *> codes_;
// a list of codes
std::vector<std::string> codes_;
// requires a code to arm
bool requires_code_to_arm_ = false;
bool supports_arm_home_ = false;

View File

@@ -88,5 +88,5 @@ async def to_code(config):
if CONF_SET_ACTION in config:
await automation.build_automation(
var.get_set_trigger(), [(cg.StringRef, "x")], config[CONF_SET_ACTION]
var.get_set_trigger(), [(cg.std_string, "x")], config[CONF_SET_ACTION]
)

View File

@@ -41,7 +41,7 @@ void TemplateSelect::update() {
}
void TemplateSelect::control(size_t index) {
this->set_trigger_->trigger(StringRef(this->option_at(index)));
this->set_trigger_->trigger(std::string(this->option_at(index)));
if (this->optimistic_)
this->publish_state(index);

View File

@@ -4,7 +4,6 @@
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/preferences.h"
#include "esphome/core/string_ref.h"
#include "esphome/core/template_lambda.h"
namespace esphome::template_ {
@@ -18,7 +17,7 @@ class TemplateSelect final : public select::Select, public PollingComponent {
void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
Trigger<StringRef> *get_set_trigger() const { return this->set_trigger_; }
Trigger<std::string> *get_set_trigger() const { return this->set_trigger_; }
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void set_initial_option_index(size_t initial_option_index) { this->initial_option_index_ = initial_option_index; }
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
@@ -28,7 +27,7 @@ class TemplateSelect final : public select::Select, public PollingComponent {
bool optimistic_ = false;
size_t initial_option_index_{0};
bool restore_value_ = false;
Trigger<StringRef> *set_trigger_ = new Trigger<StringRef>();
Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
TemplateLambda<std::string> f_;
ESPPreferenceObject pref_;

View File

@@ -8,23 +8,16 @@ static const char *const TAG = "template.text";
void TemplateText::setup() {
if (this->f_.has_value())
return;
if (this->pref_ == nullptr) {
// No restore - use const char* directly, no heap allocation needed
if (this->initial_value_ != nullptr && this->initial_value_[0] != '\0') {
ESP_LOGD(TAG, "State from initial: %s", this->initial_value_);
this->publish_state(this->initial_value_);
}
return;
std::string value = this->initial_value_;
if (!this->pref_) {
ESP_LOGD(TAG, "State from initial: %s", value.c_str());
} else {
uint32_t key = this->get_preference_hash();
key += this->traits.get_min_length() << 2;
key += this->traits.get_max_length() << 4;
key += fnv1_hash(this->traits.get_pattern_c_str()) << 6;
this->pref_->setup(key, value);
}
// Need std::string for pref_->setup() to fill from flash
std::string value{this->initial_value_ != nullptr ? this->initial_value_ : ""};
uint32_t key = this->get_preference_hash();
key += this->traits.get_min_length() << 2;
key += this->traits.get_max_length() << 4;
key += fnv1_hash(this->traits.get_pattern_c_str()) << 6;
this->pref_->setup(key, value);
if (!value.empty())
this->publish_state(value);
}

View File

@@ -70,15 +70,13 @@ class TemplateText final : public text::Text, public PollingComponent {
Trigger<std::string> *get_set_trigger() const { return this->set_trigger_; }
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void set_initial_value(const char *initial_value) { this->initial_value_ = initial_value; }
/// Prevent accidental use of std::string which would dangle
void set_initial_value(const std::string &initial_value) = delete;
void set_initial_value(const std::string &initial_value) { this->initial_value_ = initial_value; }
void set_value_saver(TemplateTextSaverBase *restore_value_saver) { this->pref_ = restore_value_saver; }
protected:
void control(const std::string &value) override;
bool optimistic_ = false;
const char *initial_value_{nullptr};
std::string initial_value_;
Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
TemplateLambda<std::string> f_{};

View File

@@ -55,7 +55,6 @@ enum MessageType : uint16_t {
COMMAND = 0x0106,
};
// Max string length: 7 ("Unknown"/"Command"). Update print() buffer sizes if adding longer strings.
inline const char *message_type_to_str(MessageType t) {
switch (t) {
case STATUS:
@@ -84,11 +83,7 @@ struct MessageHeader {
}
std::string print() {
// 64 bytes: "MessageHeader: seq " + uint16 + ", len " + uint32 + ", type " + type + safety margin
char buf[64];
buf_append_printf(buf, sizeof(buf), 0, "MessageHeader: seq %d, len %d, type %s", this->seq, this->len,
message_type_to_str(this->type));
return buf;
return str_sprintf("MessageHeader: seq %d, len %d, type %s", this->seq, this->len, message_type_to_str(this->type));
}
void byteswap() {
@@ -136,7 +131,6 @@ inline CoverOperation gate_status_to_cover_operation(GateStatus s) {
return COVER_OPERATION_IDLE;
}
// Max string length: 11 ("Ventilating"). Update print() buffer sizes if adding longer strings.
inline const char *gate_status_to_str(GateStatus s) {
switch (s) {
case PAUSED:
@@ -176,12 +170,7 @@ struct StatusReply {
GateStatus state;
uint8_t trailer = 0x0;
std::string print() {
// 48 bytes: "StatusReply: state " (19) + state (11) + safety margin
char buf[48];
buf_append_printf(buf, sizeof(buf), 0, "StatusReply: state %s", gate_status_to_str(this->state));
return buf;
}
std::string print() { return str_sprintf("StatusReply: state %s", gate_status_to_str(this->state)); }
void byteswap(){};
} __attribute__((packed));
@@ -213,12 +202,7 @@ struct CommandRequestReply {
CommandRequestReply() = default;
CommandRequestReply(GateStatus state) { this->state = state; }
std::string print() {
// 56 bytes: "CommandRequestReply: state " (27) + state (11) + safety margin
char buf[56];
buf_append_printf(buf, sizeof(buf), 0, "CommandRequestReply: state %s", gate_status_to_str(this->state));
return buf;
}
std::string print() { return str_sprintf("CommandRequestReply: state %s", gate_status_to_str(this->state)); }
void byteswap() { this->type = convert_big_endian(this->type); }
} __attribute__((packed));

View File

@@ -1,6 +1,5 @@
#include "toshiba.h"
#include "esphome/components/remote_base/toshiba_ac_protocol.h"
#include "esphome/core/helpers.h"
#include <vector>
@@ -428,17 +427,10 @@ void ToshibaClimate::setup() {
// Never send nan to HA
if (std::isnan(this->target_temperature))
this->target_temperature = 24;
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
// Log final state for debugging HA errors
const char *fan_mode_str = "NONE";
char fan_mode_buf[4]; // max 3 digits for fan mode enum + null
if (this->fan_mode.has_value()) {
buf_append_printf(fan_mode_buf, sizeof(fan_mode_buf), 0, "%d", static_cast<int>(this->fan_mode.value()));
fan_mode_str = fan_mode_buf;
}
ESP_LOGV(TAG, "Setup complete - Mode: %d, Fan: %s, Swing: %d, Temp: %.1f", static_cast<int>(this->mode), fan_mode_str,
ESP_LOGV(TAG, "Setup complete - Mode: %d, Fan: %s, Swing: %d, Temp: %.1f", static_cast<int>(this->mode),
this->fan_mode.has_value() ? std::to_string(static_cast<int>(this->fan_mode.value())).c_str() : "NONE",
static_cast<int>(this->swing_mode), this->target_temperature);
#endif
}
void ToshibaClimate::transmit_state() {

View File

@@ -191,7 +191,7 @@ void TuyaLight::write_state(light::LightState *state) {
case TuyaColorType::RGB: {
char buffer[7];
const char *format_str = this->color_type_lowercase_ ? "%02x%02x%02x" : "%02X%02X%02X";
snprintf(buffer, sizeof(buffer), format_str, int(red * 255), int(green * 255), int(blue * 255));
sprintf(buffer, format_str, int(red * 255), int(green * 255), int(blue * 255));
color_value = buffer;
break;
}
@@ -201,7 +201,7 @@ void TuyaLight::write_state(light::LightState *state) {
rgb_to_hsv(red, green, blue, hue, saturation, value);
char buffer[13];
const char *format_str = this->color_type_lowercase_ ? "%04x%04x%04x" : "%04X%04X%04X";
snprintf(buffer, sizeof(buffer), format_str, hue, int(saturation * 1000), int(value * 1000));
sprintf(buffer, format_str, hue, int(saturation * 1000), int(value * 1000));
color_value = buffer;
break;
}
@@ -211,8 +211,8 @@ void TuyaLight::write_state(light::LightState *state) {
rgb_to_hsv(red, green, blue, hue, saturation, value);
char buffer[15];
const char *format_str = this->color_type_lowercase_ ? "%02x%02x%02x%04x%02x%02x" : "%02X%02X%02X%04X%02X%02X";
snprintf(buffer, sizeof(buffer), format_str, int(red * 255), int(green * 255), int(blue * 255), hue,
int(saturation * 255), int(value * 255));
sprintf(buffer, format_str, int(red * 255), int(green * 255), int(blue * 255), hue, int(saturation * 255),
int(value * 255));
color_value = buffer;
break;
}

View File

@@ -24,7 +24,7 @@ void TuyaTextSensor::setup() {
}
case TuyaDatapointType::ENUM: {
char buf[4]; // uint8_t max is 3 digits + null
buf_append_printf(buf, sizeof(buf), 0, "%u", datapoint.value_enum);
snprintf(buf, sizeof(buf), "%u", datapoint.value_enum);
ESP_LOGD(TAG, "MCU reported text sensor %u is: %s", datapoint.id, buf);
this->publish_state(buf);
break;

View File

@@ -107,7 +107,7 @@ void UARTDebug::log_hex(UARTDirection direction, std::vector<uint8_t> bytes, uin
if (i > 0) {
res += separator;
}
buf_append_printf(buf, sizeof(buf), 0, "%02X", bytes[i]);
sprintf(buf, "%02X", bytes[i]);
res += buf;
}
ESP_LOGD(TAG, "%s", res.c_str());
@@ -147,7 +147,7 @@ void UARTDebug::log_string(UARTDirection direction, std::vector<uint8_t> bytes)
} else if (bytes[i] == 92) {
res += "\\\\";
} else if (bytes[i] < 32 || bytes[i] > 127) {
buf_append_printf(buf, sizeof(buf), 0, "\\x%02X", bytes[i]);
sprintf(buf, "\\x%02X", bytes[i]);
res += buf;
} else {
res += bytes[i];
@@ -166,13 +166,11 @@ void UARTDebug::log_int(UARTDirection direction, std::vector<uint8_t> bytes, uin
} else {
res += ">>> ";
}
char buf[4]; // max 3 digits for uint8_t (255) + null
for (size_t i = 0; i < len; i++) {
if (i > 0) {
res += separator;
}
buf_append_printf(buf, sizeof(buf), 0, "%u", bytes[i]);
res += buf;
res += to_string(bytes[i]);
}
ESP_LOGD(TAG, "%s", res.c_str());
delay(10);
@@ -191,7 +189,7 @@ void UARTDebug::log_binary(UARTDirection direction, std::vector<uint8_t> bytes,
if (i > 0) {
res += separator;
}
buf_append_printf(buf, sizeof(buf), 0, "0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(bytes[i]), bytes[i]);
sprintf(buf, "0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(bytes[i]), bytes[i]);
res += buf;
}
ESP_LOGD(TAG, "%s", res.c_str());

View File

@@ -108,7 +108,8 @@ async def to_code(config):
cg.add(var.set_broadcast_port(conf_port[CONF_BROADCAST_PORT]))
if (listen_address := str(config[CONF_LISTEN_ADDRESS])) != "255.255.255.255":
cg.add(var.set_listen_address(listen_address))
cg.add(var.set_addresses([str(addr) for addr in config[CONF_ADDRESSES]]))
for address in config[CONF_ADDRESSES]:
cg.add(var.add_address(str(address)))
if on_receive := config.get(CONF_ON_RECEIVE):
on_receive = on_receive[0]
trigger = cg.new_Pvariable(on_receive[CONF_TRIGGER_ID])

View File

@@ -5,7 +5,8 @@
#include "esphome/components/network/util.h"
#include "udp_component.h"
namespace esphome::udp {
namespace esphome {
namespace udp {
static const char *const TAG = "udp";
@@ -94,7 +95,7 @@ void UDPComponent::setup() {
// 8266 and RP2040 `Duino
for (const auto &address : this->addresses_) {
auto ipaddr = IPAddress();
ipaddr.fromString(address);
ipaddr.fromString(address.c_str());
this->ipaddrs_.push_back(ipaddr);
}
if (this->should_listen_)
@@ -129,8 +130,8 @@ void UDPComponent::dump_config() {
" Listen Port: %u\n"
" Broadcast Port: %u",
this->listen_port_, this->broadcast_port_);
for (const char *address : this->addresses_)
ESP_LOGCONFIG(TAG, " Address: %s", address);
for (const auto &address : this->addresses_)
ESP_LOGCONFIG(TAG, " Address: %s", address.c_str());
if (this->listen_address_.has_value()) {
char addr_buf[network::IP_ADDRESS_BUFFER_SIZE];
ESP_LOGCONFIG(TAG, " Listen address: %s", this->listen_address_.value().str_to(addr_buf));
@@ -161,6 +162,7 @@ void UDPComponent::send_packet(const uint8_t *data, size_t size) {
}
#endif
}
} // namespace esphome::udp
} // namespace udp
} // namespace esphome
#endif

View File

@@ -2,7 +2,6 @@
#include "esphome/core/defines.h"
#ifdef USE_NETWORK
#include "esphome/core/helpers.h"
#include "esphome/components/network/ip_address.h"
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
#include "esphome/components/socket/socket.h"
@@ -10,17 +9,15 @@
#ifdef USE_SOCKET_IMPL_LWIP_TCP
#include <WiFiUdp.h>
#endif
#include <initializer_list>
#include <vector>
namespace esphome::udp {
namespace esphome {
namespace udp {
static const size_t MAX_PACKET_SIZE = 508;
class UDPComponent : public Component {
public:
void set_addresses(std::initializer_list<const char *> addresses) { this->addresses_ = addresses; }
/// Prevent accidental use of std::string which would dangle
void set_addresses(std::initializer_list<std::string> addresses) = delete;
void add_address(const char *addr) { this->addresses_.emplace_back(addr); }
void set_listen_address(const char *listen_addr) { this->listen_address_ = network::IPAddress(listen_addr); }
void set_listen_port(uint16_t port) { this->listen_port_ = port; }
void set_broadcast_port(uint16_t port) { this->broadcast_port_ = port; }
@@ -52,10 +49,11 @@ class UDPComponent : public Component {
std::vector<IPAddress> ipaddrs_{};
WiFiUDP udp_client_{};
#endif
FixedVector<const char *> addresses_{};
std::vector<std::string> addresses_{};
optional<network::IPAddress> listen_address_{};
};
} // namespace esphome::udp
} // namespace udp
} // namespace esphome
#endif

View File

@@ -9,12 +9,17 @@ namespace uptime {
static const char *const TAG = "uptime.sensor";
// Clamp position to valid buffer range when snprintf indicates truncation
static size_t clamp_buffer_pos(size_t pos, size_t buf_size) { return pos < buf_size ? pos : buf_size - 1; }
static void append_unit(char *buf, size_t buf_size, size_t &pos, const char *separator, unsigned value,
const char *label) {
if (pos > 0) {
pos = buf_append_printf(buf, buf_size, pos, "%s", separator);
pos += snprintf(buf + pos, buf_size - pos, "%s", separator);
pos = clamp_buffer_pos(pos, buf_size);
}
pos = buf_append_printf(buf, buf_size, pos, "%u%s", value, label);
pos += snprintf(buf + pos, buf_size - pos, "%u%s", value, label);
pos = clamp_buffer_pos(pos, buf_size);
}
void UptimeTextSensor::setup() {

View File

@@ -81,7 +81,7 @@ struct Timer {
this->id.c_str(), this->name.c_str(), this->total_seconds, this->seconds_left, YESNO(this->is_active));
return buffer.data();
}
std::string to_string() const { // NOLINT
std::string to_string() const {
char buffer[TO_STR_BUFFER_SIZE];
return this->to_str(buffer);
}

View File

@@ -1188,7 +1188,11 @@ std::string WebServer::date_json_(datetime::DateEntity *obj, JsonDetail start_co
// Format: YYYY-MM-DD (max 10 chars + null)
char value[12];
buf_append_printf(value, sizeof(value), 0, "%d-%02d-%02d", obj->year, obj->month, obj->day);
#ifdef USE_ESP8266
snprintf_P(value, sizeof(value), PSTR("%d-%02d-%02d"), obj->year, obj->month, obj->day);
#else
snprintf(value, sizeof(value), "%d-%02d-%02d", obj->year, obj->month, obj->day);
#endif
set_json_icon_state_value(root, obj, "date", value, value, start_config);
if (start_config == DETAIL_ALL) {
this->add_sorting_info_(root, obj);
@@ -1247,7 +1251,11 @@ std::string WebServer::time_json_(datetime::TimeEntity *obj, JsonDetail start_co
// Format: HH:MM:SS (8 chars + null)
char value[12];
buf_append_printf(value, sizeof(value), 0, "%02d:%02d:%02d", obj->hour, obj->minute, obj->second);
#ifdef USE_ESP8266
snprintf_P(value, sizeof(value), PSTR("%02d:%02d:%02d"), obj->hour, obj->minute, obj->second);
#else
snprintf(value, sizeof(value), "%02d:%02d:%02d", obj->hour, obj->minute, obj->second);
#endif
set_json_icon_state_value(root, obj, "time", value, value, start_config);
if (start_config == DETAIL_ALL) {
this->add_sorting_info_(root, obj);
@@ -1306,8 +1314,13 @@ std::string WebServer::datetime_json_(datetime::DateTimeEntity *obj, JsonDetail
// Format: YYYY-MM-DD HH:MM:SS (max 19 chars + null)
char value[24];
buf_append_printf(value, sizeof(value), 0, "%d-%02d-%02d %02d:%02d:%02d", obj->year, obj->month, obj->day, obj->hour,
obj->minute, obj->second);
#ifdef USE_ESP8266
snprintf_P(value, sizeof(value), PSTR("%d-%02d-%02d %02d:%02d:%02d"), obj->year, obj->month, obj->day, obj->hour,
obj->minute, obj->second);
#else
snprintf(value, sizeof(value), "%d-%02d-%02d %02d:%02d:%02d", obj->year, obj->month, obj->day, obj->hour, obj->minute,
obj->second);
#endif
set_json_icon_state_value(root, obj, "datetime", value, value, start_config);
if (start_config == DETAIL_ALL) {
this->add_sorting_info_(root, obj);

View File

@@ -4,13 +4,19 @@
/// @details The classes declared in this file can be used by the Weikai family
#include "weikai.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace weikai {
static const char *const TAG = "weikai";
/// @brief convert an int to binary representation as C++ std::string
/// @param val integer to convert
/// @return a std::string
inline std::string i2s(uint8_t val) { return std::bitset<8>(val).to_string(); }
/// Convert std::string to C string
#define I2S2CS(val) (i2s(val).c_str())
/// @brief measure the time elapsed between two calls
/// @param last_time time of the previous call
/// @return the elapsed time in milliseconds
@@ -164,18 +170,17 @@ void WeikaiComponent::test_gpio_input_() {
static bool init_input{false};
static uint8_t state{0};
uint8_t value;
char bin_buf[9]; // 8 binary digits + null
if (!init_input) {
init_input = true;
// set all pins in input mode
this->reg(WKREG_GPDIR, 0) = 0x00;
ESP_LOGI(TAG, "initializing all pins to input mode");
state = this->reg(WKREG_GPDAT, 0);
ESP_LOGI(TAG, "initial input data state = %02X (%s)", state, format_bin_to(bin_buf, state));
ESP_LOGI(TAG, "initial input data state = %02X (%s)", state, I2S2CS(state));
}
value = this->reg(WKREG_GPDAT, 0);
if (value != state) {
ESP_LOGI(TAG, "Input data changed from %02X to %02X (%s)", state, value, format_bin_to(bin_buf, value));
ESP_LOGI(TAG, "Input data changed from %02X to %02X (%s)", state, value, I2S2CS(value));
state = value;
}
}
@@ -183,7 +188,6 @@ void WeikaiComponent::test_gpio_input_() {
void WeikaiComponent::test_gpio_output_() {
static bool init_output{false};
static uint8_t state{0};
char bin_buf[9]; // 8 binary digits + null
if (!init_output) {
init_output = true;
// set all pins in output mode
@@ -194,7 +198,7 @@ void WeikaiComponent::test_gpio_output_() {
}
state = ~state;
this->reg(WKREG_GPDAT, 0) = state;
ESP_LOGI(TAG, "Flipping all outputs to %02X (%s)", state, format_bin_to(bin_buf, state));
ESP_LOGI(TAG, "Flipping all outputs to %02X (%s)", state, I2S2CS(state));
delay(100); // NOLINT
}
#endif
@@ -204,9 +208,7 @@ void WeikaiComponent::test_gpio_output_() {
///////////////////////////////////////////////////////////////////////////////
bool WeikaiComponent::read_pin_val_(uint8_t pin) {
this->input_state_ = this->reg(WKREG_GPDAT, 0);
char bin_buf[9];
ESP_LOGVV(TAG, "reading input pin %u = %u in_state %s", pin, this->input_state_ & (1 << pin),
format_bin_to(bin_buf, this->input_state_));
ESP_LOGVV(TAG, "reading input pin %u = %u in_state %s", pin, this->input_state_ & (1 << pin), I2S2CS(input_state_));
return this->input_state_ & (1 << pin);
}
@@ -216,9 +218,7 @@ void WeikaiComponent::write_pin_val_(uint8_t pin, bool value) {
} else {
this->output_state_ &= ~(1 << pin);
}
char bin_buf[9];
ESP_LOGVV(TAG, "writing output pin %d with %d out_state %s", pin, uint8_t(value),
format_bin_to(bin_buf, this->output_state_));
ESP_LOGVV(TAG, "writing output pin %d with %d out_state %s", pin, uint8_t(value), I2S2CS(this->output_state_));
this->reg(WKREG_GPDAT, 0) = this->output_state_;
}
@@ -232,8 +232,7 @@ void WeikaiComponent::set_pin_direction_(uint8_t pin, gpio::Flags flags) {
ESP_LOGE(TAG, "pin %d direction invalid", pin);
}
}
char bin_buf[9];
ESP_LOGVV(TAG, "setting pin %d direction to %d pin_config=%s", pin, flags, format_bin_to(bin_buf, this->pin_config_));
ESP_LOGVV(TAG, "setting pin %d direction to %d pin_config=%s", pin, flags, I2S2CS(this->pin_config_));
this->reg(WKREG_GPDIR, 0) = this->pin_config_; // TODO check ~
}
@@ -242,11 +241,12 @@ void WeikaiGPIOPin::setup() {
flags_ == gpio::FLAG_INPUT ? "Input"
: this->flags_ == gpio::FLAG_OUTPUT ? "Output"
: "NOT SPECIFIED");
// ESP_LOGCONFIG(TAG, "Setting GPIO pins mode to '%s' %02X", I2S2CS(this->flags_), this->flags_);
this->pin_mode(this->flags_);
}
size_t WeikaiGPIOPin::dump_summary(char *buffer, size_t len) const {
return buf_append_printf(buffer, len, 0, "%u via WeiKai %s", this->pin_, this->parent_->get_name());
return snprintf(buffer, len, "%u via WeiKai %s", this->pin_, this->parent_->get_name());
}
///////////////////////////////////////////////////////////////////////////////
@@ -297,9 +297,8 @@ void WeikaiChannel::set_line_param_() {
break; // no parity 000x
}
this->reg(WKREG_LCR) = lcr; // write LCR
char bin_buf[9];
ESP_LOGV(TAG, " line config: %d data_bits, %d stop_bits, parity %s register [%s]", this->data_bits_,
this->stop_bits_, p2s(this->parity_), format_bin_to(bin_buf, lcr));
this->stop_bits_, p2s(this->parity_), I2S2CS(lcr));
}
void WeikaiChannel::set_baudrate_() {
@@ -335,8 +334,7 @@ size_t WeikaiChannel::tx_in_fifo_() {
if (tfcnt == 0) {
uint8_t const fsr = this->reg(WKREG_FSR);
if (fsr & FSR_TFFULL) {
char bin_buf[9];
ESP_LOGVV(TAG, "tx FIFO full FSR=%s", format_bin_to(bin_buf, fsr));
ESP_LOGVV(TAG, "tx FIFO full FSR=%s", I2S2CS(fsr));
tfcnt = FIFO_SIZE;
}
}
@@ -348,15 +346,14 @@ size_t WeikaiChannel::rx_in_fifo_() {
size_t available = this->reg(WKREG_RFCNT);
uint8_t const fsr = this->reg(WKREG_FSR);
if (fsr & (FSR_RFOE | FSR_RFLB | FSR_RFFE | FSR_RFPE)) {
char bin_buf[9];
if (fsr & FSR_RFOE)
ESP_LOGE(TAG, "Receive data overflow FSR=%s", format_bin_to(bin_buf, fsr));
ESP_LOGE(TAG, "Receive data overflow FSR=%s", I2S2CS(fsr));
if (fsr & FSR_RFLB)
ESP_LOGE(TAG, "Receive line break FSR=%s", format_bin_to(bin_buf, fsr));
ESP_LOGE(TAG, "Receive line break FSR=%s", I2S2CS(fsr));
if (fsr & FSR_RFFE)
ESP_LOGE(TAG, "Receive frame error FSR=%s", format_bin_to(bin_buf, fsr));
ESP_LOGE(TAG, "Receive frame error FSR=%s", I2S2CS(fsr));
if (fsr & FSR_RFPE)
ESP_LOGE(TAG, "Receive parity error FSR=%s", format_bin_to(bin_buf, fsr));
ESP_LOGE(TAG, "Receive parity error FSR=%s", I2S2CS(fsr));
}
if ((available == 0) && (fsr & FSR_RFDAT)) {
// here we should be very careful because we can have something like this:
@@ -365,13 +362,11 @@ size_t WeikaiChannel::rx_in_fifo_() {
// - so to be sure we need to do another read of RFCNT and if it is still zero -> buffer full
available = this->reg(WKREG_RFCNT);
if (available == 0) { // still zero ?
char bin_buf[9];
ESP_LOGV(TAG, "rx FIFO is full FSR=%s", format_bin_to(bin_buf, fsr));
ESP_LOGV(TAG, "rx FIFO is full FSR=%s", I2S2CS(fsr));
available = FIFO_SIZE;
}
}
char bin_buf2[9];
ESP_LOGVV(TAG, "rx FIFO contain %d bytes - FSR status=%s", available, format_bin_to(bin_buf2, fsr));
ESP_LOGVV(TAG, "rx FIFO contain %d bytes - FSR status=%s", available, I2S2CS(fsr));
return available;
}

View File

@@ -8,6 +8,7 @@
/// wk2132_i2c, wk2168_i2c, wk2204_i2c, wk2212_i2c
#pragma once
#include <bitset>
#include <memory>
#include <cinttypes>
#include "esphome/core/component.h"

View File

@@ -10,6 +10,13 @@ namespace weikai_spi {
using namespace weikai;
static const char *const TAG = "weikai_spi";
/// @brief convert an int to binary representation as C++ std::string
/// @param val integer to convert
/// @return a std::string
inline std::string i2s(uint8_t val) { return std::bitset<8>(val).to_string(); }
/// Convert std::string to C string
#define I2S2CS(val) (i2s(val).c_str())
/// @brief measure the time elapsed between two calls
/// @param last_time time of the previous call
/// @return the elapsed time in microseconds
@@ -100,8 +107,7 @@ uint8_t WeikaiRegisterSPI::read_reg() const {
spi_comp->write_byte(cmd);
uint8_t val = spi_comp->read_byte();
spi_comp->disable();
char bin_buf[9];
ESP_LOGVV(TAG, "WeikaiRegisterSPI::read_reg() cmd=%s(%02X) reg=%s ch=%d buf=%02X", format_bin_to(bin_buf, cmd), cmd,
ESP_LOGVV(TAG, "WeikaiRegisterSPI::read_reg() cmd=%s(%02X) reg=%s ch=%d buf=%02X", I2S2CS(cmd), cmd,
reg_to_str(this->register_, this->comp_->page1()), this->channel_, val);
return val;
}
@@ -114,9 +120,8 @@ void WeikaiRegisterSPI::read_fifo(uint8_t *data, size_t length) const {
spi_comp->read_array(data, length);
spi_comp->disable();
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
char bin_buf[9];
ESP_LOGVV(TAG, "WeikaiRegisterSPI::read_fifo() cmd=%s(%02X) ch=%d len=%d buffer", format_bin_to(bin_buf, cmd), cmd,
this->channel_, length);
ESP_LOGVV(TAG, "WeikaiRegisterSPI::read_fifo() cmd=%s(%02X) ch=%d len=%d buffer", I2S2CS(cmd), cmd, this->channel_,
length);
print_buffer(data, length);
#endif
}
@@ -127,9 +132,8 @@ void WeikaiRegisterSPI::write_reg(uint8_t value) {
spi_comp->enable();
spi_comp->write_array(buf, 2);
spi_comp->disable();
char bin_buf[9];
ESP_LOGVV(TAG, "WeikaiRegisterSPI::write_reg() cmd=%s(%02X) reg=%s ch=%d buf=%02X", format_bin_to(bin_buf, buf[0]),
buf[0], reg_to_str(this->register_, this->comp_->page1()), this->channel_, buf[1]);
ESP_LOGVV(TAG, "WeikaiRegisterSPI::write_reg() cmd=%s(%02X) reg=%s ch=%d buf=%02X", I2S2CS(buf[0]), buf[0],
reg_to_str(this->register_, this->comp_->page1()), this->channel_, buf[1]);
}
void WeikaiRegisterSPI::write_fifo(uint8_t *data, size_t length) {
@@ -141,9 +145,8 @@ void WeikaiRegisterSPI::write_fifo(uint8_t *data, size_t length) {
spi_comp->disable();
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
char bin_buf[9];
ESP_LOGVV(TAG, "WeikaiRegisterSPI::write_fifo() cmd=%s(%02X) ch=%d len=%d buffer", format_bin_to(bin_buf, cmd), cmd,
this->channel_, length);
ESP_LOGVV(TAG, "WeikaiRegisterSPI::write_fifo() cmd=%s(%02X) ch=%d len=%d buffer", I2S2CS(cmd), cmd, this->channel_,
length);
print_buffer(data, length);
#endif
}

View File

@@ -6,6 +6,7 @@
/// wk2124_spi, wk2132_spi, wk2168_spi, wk2204_spi, wk2212_spi,
#pragma once
#include <bitset>
#include <memory>
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"

View File

@@ -1,5 +1,4 @@
#include "wiegand.h"
#include <cinttypes>
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
@@ -70,35 +69,32 @@ void Wiegand::loop() {
for (auto *trigger : this->raw_triggers_)
trigger->trigger(count, value);
if (count == 26) {
char tag_buf[12]; // max 8 digits for 24-bit value + null
buf_append_printf(tag_buf, sizeof(tag_buf), 0, "%" PRIu32, static_cast<uint32_t>((value >> 1) & 0xffffff));
ESP_LOGD(TAG, "received 26-bit tag: %s", tag_buf);
std::string tag = to_string((value >> 1) & 0xffffff);
ESP_LOGD(TAG, "received 26-bit tag: %s", tag.c_str());
if (!check_eparity(value, 13, 13) || !check_oparity(value, 0, 13)) {
ESP_LOGW(TAG, "invalid parity");
return;
}
for (auto *trigger : this->tag_triggers_)
trigger->trigger(tag_buf);
trigger->trigger(tag);
} else if (count == 34) {
char tag_buf[12]; // max 10 digits for 32-bit value + null
buf_append_printf(tag_buf, sizeof(tag_buf), 0, "%" PRIu32, static_cast<uint32_t>((value >> 1) & 0xffffffff));
ESP_LOGD(TAG, "received 34-bit tag: %s", tag_buf);
std::string tag = to_string((value >> 1) & 0xffffffff);
ESP_LOGD(TAG, "received 34-bit tag: %s", tag.c_str());
if (!check_eparity(value, 17, 17) || !check_oparity(value, 0, 17)) {
ESP_LOGW(TAG, "invalid parity");
return;
}
for (auto *trigger : this->tag_triggers_)
trigger->trigger(tag_buf);
trigger->trigger(tag);
} else if (count == 37) {
char tag_buf[12]; // max 11 digits for 35-bit value + null
buf_append_printf(tag_buf, sizeof(tag_buf), 0, "%" PRIu64, static_cast<uint64_t>((value >> 1) & 0x7ffffffff));
ESP_LOGD(TAG, "received 37-bit tag: %s", tag_buf);
std::string tag = to_string((value >> 1) & 0x7ffffffff);
ESP_LOGD(TAG, "received 37-bit tag: %s", tag.c_str());
if (!check_eparity(value, 18, 19) || !check_oparity(value, 0, 19)) {
ESP_LOGW(TAG, "invalid parity");
return;
}
for (auto *trigger : this->tag_triggers_)
trigger->trigger(tag_buf);
trigger->trigger(tag);
} else if (count == 4) {
for (auto *trigger : this->key_triggers_)
trigger->trigger(value);

View File

@@ -30,7 +30,6 @@ _WG_KEY_REGEX = re.compile(r"^[A-Za-z0-9+/]{42}[AEIMQUYcgkosw480]=$")
wireguard_ns = cg.esphome_ns.namespace("wireguard")
Wireguard = wireguard_ns.class_("Wireguard", cg.Component, cg.PollingComponent)
AllowedIP = wireguard_ns.struct("AllowedIP")
WireguardPeerOnlineCondition = wireguard_ns.class_(
"WireguardPeerOnlineCondition", automation.Condition
)
@@ -109,18 +108,8 @@ async def to_code(config):
)
)
cg.add(
var.set_allowed_ips(
[
cg.StructInitializer(
AllowedIP,
("ip", str(ip.network_address)),
("netmask", str(ip.netmask)),
)
for ip in allowed_ips
]
)
)
for ip in allowed_ips:
cg.add(var.add_allowed_ip(str(ip.network_address), str(ip.netmask)))
cg.add(var.set_srctime(await cg.get_variable(config[CONF_TIME_ID])))

View File

@@ -13,7 +13,8 @@
#include <esp_wireguard.h>
#include <esp_wireguard_err.h>
namespace esphome::wireguard {
namespace esphome {
namespace wireguard {
static const char *const TAG = "wireguard";
@@ -27,16 +28,16 @@ static const char *const LOGMSG_ONLINE = "online";
static const char *const LOGMSG_OFFLINE = "offline";
void Wireguard::setup() {
this->wg_config_.address = this->address_;
this->wg_config_.private_key = this->private_key_;
this->wg_config_.endpoint = this->peer_endpoint_;
this->wg_config_.public_key = this->peer_public_key_;
this->wg_config_.address = this->address_.c_str();
this->wg_config_.private_key = this->private_key_.c_str();
this->wg_config_.endpoint = this->peer_endpoint_.c_str();
this->wg_config_.public_key = this->peer_public_key_.c_str();
this->wg_config_.port = this->peer_port_;
this->wg_config_.netmask = this->netmask_;
this->wg_config_.netmask = this->netmask_.c_str();
this->wg_config_.persistent_keepalive = this->keepalive_;
if (this->preshared_key_ != nullptr)
this->wg_config_.preshared_key = this->preshared_key_;
if (!this->preshared_key_.empty())
this->wg_config_.preshared_key = this->preshared_key_.c_str();
this->publish_enabled_state();
@@ -130,10 +131,6 @@ void Wireguard::update() {
}
void Wireguard::dump_config() {
char private_key_masked[MASK_KEY_BUFFER_SIZE];
char preshared_key_masked[MASK_KEY_BUFFER_SIZE];
mask_key_to(private_key_masked, sizeof(private_key_masked), this->private_key_);
mask_key_to(preshared_key_masked, sizeof(preshared_key_masked), this->preshared_key_);
// clang-format off
ESP_LOGCONFIG(
TAG,
@@ -145,13 +142,13 @@ void Wireguard::dump_config() {
" Peer Port: " LOG_SECRET("%d") "\n"
" Peer Public Key: " LOG_SECRET("%s") "\n"
" Peer Pre-shared Key: " LOG_SECRET("%s"),
this->address_, this->netmask_, private_key_masked,
this->peer_endpoint_, this->peer_port_, this->peer_public_key_,
(this->preshared_key_ != nullptr ? preshared_key_masked : "NOT IN USE"));
this->address_.c_str(), this->netmask_.c_str(), mask_key(this->private_key_).c_str(),
this->peer_endpoint_.c_str(), this->peer_port_, this->peer_public_key_.c_str(),
(!this->preshared_key_.empty() ? mask_key(this->preshared_key_).c_str() : "NOT IN USE"));
// clang-format on
ESP_LOGCONFIG(TAG, " Peer Allowed IPs:");
for (const AllowedIP &allowed_ip : this->allowed_ips_) {
ESP_LOGCONFIG(TAG, " - %s/%s", allowed_ip.ip, allowed_ip.netmask);
for (auto &allowed_ip : this->allowed_ips_) {
ESP_LOGCONFIG(TAG, " - %s/%s", std::get<0>(allowed_ip).c_str(), std::get<1>(allowed_ip).c_str());
}
ESP_LOGCONFIG(TAG, " Peer Persistent Keepalive: %d%s", this->keepalive_,
(this->keepalive_ > 0 ? "s" : " (DISABLED)"));
@@ -179,6 +176,18 @@ time_t Wireguard::get_latest_handshake() const {
return result;
}
void Wireguard::set_address(const std::string &address) { this->address_ = address; }
void Wireguard::set_netmask(const std::string &netmask) { this->netmask_ = netmask; }
void Wireguard::set_private_key(const std::string &key) { this->private_key_ = key; }
void Wireguard::set_peer_endpoint(const std::string &endpoint) { this->peer_endpoint_ = endpoint; }
void Wireguard::set_peer_public_key(const std::string &key) { this->peer_public_key_ = key; }
void Wireguard::set_peer_port(const uint16_t port) { this->peer_port_ = port; }
void Wireguard::set_preshared_key(const std::string &key) { this->preshared_key_ = key; }
void Wireguard::add_allowed_ip(const std::string &ip, const std::string &netmask) {
this->allowed_ips_.emplace_back(ip, netmask);
}
void Wireguard::set_keepalive(const uint16_t seconds) { this->keepalive_ = seconds; }
void Wireguard::set_reboot_timeout(const uint32_t seconds) { this->reboot_timeout_ = seconds; }
void Wireguard::set_srctime(time::RealTimeClock *srctime) { this->srctime_ = srctime; }
@@ -265,8 +274,9 @@ void Wireguard::start_connection_() {
ESP_LOGD(TAG, "Configuring allowed IPs list");
bool allowed_ips_ok = true;
for (const AllowedIP &ip : this->allowed_ips_) {
allowed_ips_ok &= (esp_wireguard_add_allowed_ip(&(this->wg_ctx_), ip.ip, ip.netmask) == ESP_OK);
for (std::tuple<std::string, std::string> ip : this->allowed_ips_) {
allowed_ips_ok &=
(esp_wireguard_add_allowed_ip(&(this->wg_ctx_), std::get<0>(ip).c_str(), std::get<1>(ip).c_str()) == ESP_OK);
}
if (allowed_ips_ok) {
@@ -289,25 +299,8 @@ void Wireguard::stop_connection_() {
}
}
void mask_key_to(char *buffer, size_t len, const char *key) {
// Format: "XXXXX[...]=\0" = MASK_KEY_BUFFER_SIZE chars minimum
if (len < MASK_KEY_BUFFER_SIZE || key == nullptr) {
if (len > 0)
buffer[0] = '\0';
return;
}
// Copy first 5 characters of the key
size_t i = 0;
for (; i < 5 && key[i] != '\0'; ++i) {
buffer[i] = key[i];
}
// Append "[...]="
const char *suffix = "[...]=";
for (size_t j = 0; suffix[j] != '\0' && (i + j) < len - 1; ++j) {
buffer[i + j] = suffix[j];
}
buffer[i + 6] = '\0';
}
std::string mask_key(const std::string &key) { return (key.substr(0, 5) + "[...]="); }
} // namespace esphome::wireguard
} // namespace wireguard
} // namespace esphome
#endif

View File

@@ -2,10 +2,10 @@
#include "esphome/core/defines.h"
#ifdef USE_WIREGUARD
#include <ctime>
#include <initializer_list>
#include <vector>
#include <tuple>
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/components/time/real_time_clock.h"
#ifdef USE_BINARY_SENSOR
@@ -22,13 +22,8 @@
#include <esp_wireguard.h>
namespace esphome::wireguard {
/// Allowed IP entry for WireGuard peer configuration.
struct AllowedIP {
const char *ip;
const char *netmask;
};
namespace esphome {
namespace wireguard {
/// Main Wireguard component class.
class Wireguard : public PollingComponent {
@@ -42,25 +37,15 @@ class Wireguard : public PollingComponent {
float get_setup_priority() const override { return esphome::setup_priority::BEFORE_CONNECTION; }
void set_address(const char *address) { this->address_ = address; }
void set_netmask(const char *netmask) { this->netmask_ = netmask; }
void set_private_key(const char *key) { this->private_key_ = key; }
void set_peer_endpoint(const char *endpoint) { this->peer_endpoint_ = endpoint; }
void set_peer_public_key(const char *key) { this->peer_public_key_ = key; }
void set_peer_port(uint16_t port) { this->peer_port_ = port; }
void set_preshared_key(const char *key) { this->preshared_key_ = key; }
void set_address(const std::string &address);
void set_netmask(const std::string &netmask);
void set_private_key(const std::string &key);
void set_peer_endpoint(const std::string &endpoint);
void set_peer_public_key(const std::string &key);
void set_peer_port(uint16_t port);
void set_preshared_key(const std::string &key);
/// Prevent accidental use of std::string which would dangle
void set_address(const std::string &address) = delete;
void set_netmask(const std::string &netmask) = delete;
void set_private_key(const std::string &key) = delete;
void set_peer_endpoint(const std::string &endpoint) = delete;
void set_peer_public_key(const std::string &key) = delete;
void set_preshared_key(const std::string &key) = delete;
void set_allowed_ips(std::initializer_list<AllowedIP> ips) { this->allowed_ips_ = ips; }
/// Prevent accidental use of std::string which would dangle
void set_allowed_ips(std::initializer_list<std::tuple<std::string, std::string>> ips) = delete;
void add_allowed_ip(const std::string &ip, const std::string &netmask);
void set_keepalive(uint16_t seconds);
void set_reboot_timeout(uint32_t seconds);
@@ -98,14 +83,14 @@ class Wireguard : public PollingComponent {
time_t get_latest_handshake() const;
protected:
const char *address_{nullptr};
const char *netmask_{nullptr};
const char *private_key_{nullptr};
const char *peer_endpoint_{nullptr};
const char *peer_public_key_{nullptr};
const char *preshared_key_{nullptr};
std::string address_;
std::string netmask_;
std::string private_key_;
std::string peer_endpoint_;
std::string peer_public_key_;
std::string preshared_key_;
FixedVector<AllowedIP> allowed_ips_;
std::vector<std::tuple<std::string, std::string>> allowed_ips_;
uint16_t peer_port_;
uint16_t keepalive_;
@@ -157,11 +142,8 @@ class Wireguard : public PollingComponent {
void suspend_wdt();
void resume_wdt();
/// Size of buffer required for mask_key_to: 5 chars + "[...]=" + null = 12
static constexpr size_t MASK_KEY_BUFFER_SIZE = 12;
/// Strip most part of the key only for secure printing
void mask_key_to(char *buffer, size_t len, const char *key);
std::string mask_key(const std::string &key);
/// Condition to check if remote peer is online.
template<typename... Ts> class WireguardPeerOnlineCondition : public Condition<Ts...>, public Parented<Wireguard> {
@@ -187,5 +169,6 @@ template<typename... Ts> class WireguardDisableAction : public Action<Ts...>, pu
void play(const Ts &...x) override { this->parent_->disable(); }
};
} // namespace esphome::wireguard
} // namespace wireguard
} // namespace esphome
#endif

View File

@@ -1,5 +1,4 @@
#include "wl_134.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <cinttypes>
@@ -79,8 +78,8 @@ Wl134Component::Rfid134Error Wl134Component::read_packet_() {
reading.id, reading.country, reading.isData ? "true" : "false", reading.isAnimal ? "true" : "false",
reading.reserved0, reading.reserved1);
char buf[20]; // "%03d" (3) + "%012" PRId64 (12) + null = 16 max
buf_append_printf(buf, sizeof(buf), 0, "%03d%012" PRId64, reading.country, reading.id);
char buf[20];
sprintf(buf, "%03d%012lld", reading.country, reading.id);
this->publish_state(buf);
if (this->do_reset_) {
this->set_timeout(1000, [this]() { this->publish_state(""); });

View File

@@ -111,7 +111,7 @@ void XL9535Component::pin_mode(uint8_t pin, gpio::Flags mode) {
void XL9535GPIOPin::setup() { this->pin_mode(this->flags_); }
size_t XL9535GPIOPin::dump_summary(char *buffer, size_t len) const {
return buf_append_printf(buffer, len, 0, "%u via XL9535", this->pin_);
return snprintf(buffer, len, "%u via XL9535", this->pin_);
}
void XL9535GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }

View File

@@ -5,8 +5,6 @@
#include "esphome/core/preferences.h"
#include "esphome/core/log.h"
#include <zephyr/settings/settings.h>
#include <cinttypes>
#include <cstring>
namespace esphome {
namespace zephyr {
@@ -15,9 +13,6 @@ static const char *const TAG = "zephyr.preferences";
#define ESPHOME_SETTINGS_KEY "esphome"
// Buffer size for key: "esphome/" (8) + max hex uint32 (8) + null terminator (1) = 17; use 20 for safety margin
static constexpr size_t KEY_BUFFER_SIZE = 20;
class ZephyrPreferenceBackend : public ESPPreferenceBackend {
public:
ZephyrPreferenceBackend(uint32_t type) { this->type_ = type; }
@@ -32,9 +27,7 @@ class ZephyrPreferenceBackend : public ESPPreferenceBackend {
bool load(uint8_t *data, size_t len) override {
if (len != this->data.size()) {
char key_buf[KEY_BUFFER_SIZE];
this->format_key(key_buf, sizeof(key_buf));
ESP_LOGE(TAG, "size of setting key %s changed, from: %u, to: %u", key_buf, this->data.size(), len);
ESP_LOGE(TAG, "size of setting key %s changed, from: %u, to: %u", get_key().c_str(), this->data.size(), len);
return false;
}
std::memcpy(data, this->data.data(), len);
@@ -43,7 +36,7 @@ class ZephyrPreferenceBackend : public ESPPreferenceBackend {
}
uint32_t get_type() const { return this->type_; }
void format_key(char *buf, size_t size) const { snprintf(buf, size, ESPHOME_SETTINGS_KEY "/%" PRIx32, this->type_); }
std::string get_key() const { return str_sprintf(ESPHOME_SETTINGS_KEY "/%" PRIx32, this->type_); }
std::vector<uint8_t> data;
@@ -92,9 +85,7 @@ class ZephyrPreferences : public ESPPreferences {
}
printf("type %u size %u\n", type, this->backends_.size());
auto *pref = new ZephyrPreferenceBackend(type); // NOLINT(cppcoreguidelines-owning-memory)
char key_buf[KEY_BUFFER_SIZE];
pref->format_key(key_buf, sizeof(key_buf));
ESP_LOGD(TAG, "Add new setting %s.", key_buf);
ESP_LOGD(TAG, "Add new setting %s.", pref->get_key().c_str());
this->backends_.push_back(pref);
return ESPPreferenceObject(pref);
}
@@ -143,10 +134,9 @@ class ZephyrPreferences : public ESPPreferences {
static int export_settings(int (*cb)(const char *name, const void *value, size_t val_len)) {
for (auto *backend : static_cast<ZephyrPreferences *>(global_preferences)->backends_) {
char name[KEY_BUFFER_SIZE];
backend->format_key(name, sizeof(name));
int err = cb(name, backend->data.data(), backend->data.size());
ESP_LOGD(TAG, "save in flash, name %s, len %u, err %d", name, backend->data.size(), err);
auto name = backend->get_key();
int err = cb(name.c_str(), backend->data.data(), backend->data.size());
ESP_LOGD(TAG, "save in flash, name %s, len %u, err %d", name.c_str(), backend->data.size(), err);
}
return 0;
}

Some files were not shown because too many files have changed in this diff Show More