Compare commits

..

2 Commits

Author SHA1 Message Date
J. Nick Koston
627a9bb08b refactor restore_state_ to eliminate awkward setup delegation 2026-01-24 14:51:13 -10:00
dhoeben
0aee4e1d10 Fix 2026-01-24 23:09:31 +01:00
56 changed files with 332 additions and 799 deletions

View File

@@ -1 +1 @@
d565b0589e35e692b5f2fc0c14723a99595b4828a3a3ef96c442e86a23176c00
15dc295268b2dcf75942f42759b3ddec64eba89f75525698eb39c95a7f4b14ce

View File

@@ -58,7 +58,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
uses: github/codeql-action/init@19b2f06db2b6f5108140aeb04014ef02b648f789 # v4.31.11
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -86,6 +86,6 @@ jobs:
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
uses: github/codeql-action/analyze@19b2f06db2b6f5108140aeb04014ef02b648f789 # v4.31.11
with:
category: "/language:${{matrix.language}}"

View File

@@ -1,11 +1,11 @@
#include "cover.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include <strings.h>
#include "esphome/core/log.h"
namespace esphome::cover {
static const char *const TAG = "cover";
@@ -39,13 +39,13 @@ Cover::Cover() : position{COVER_OPEN} {}
CoverCall::CoverCall(Cover *parent) : parent_(parent) {}
CoverCall &CoverCall::set_command(const char *command) {
if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("OPEN")) == 0) {
if (strcasecmp(command, "OPEN") == 0) {
this->set_command_open();
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("CLOSE")) == 0) {
} else if (strcasecmp(command, "CLOSE") == 0) {
this->set_command_close();
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("STOP")) == 0) {
} else if (strcasecmp(command, "STOP") == 0) {
this->set_command_stop();
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("TOGGLE")) == 0) {
} else if (strcasecmp(command, "TOGGLE") == 0) {
this->set_command_toggle();
} else {
ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command);

View File

@@ -12,7 +12,6 @@ from esphome.const import (
KEY_FRAMEWORK_VERSION,
)
from esphome.core import CORE
from esphome.cpp_generator import add_define
CODEOWNERS = ["@swoboda1337"]
@@ -43,7 +42,6 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config):
add_define("USE_ESP32_HOSTED")
if config[CONF_ACTIVE_HIGH]:
esp32.add_idf_sdkconfig_option(
"CONFIG_ESP_HOSTED_SDIO_RESET_ACTIVE_HIGH",

View File

@@ -126,7 +126,7 @@ CONFIG_SCHEMA = cv.All(
),
cv.Optional(CONF_CA_CERTIFICATE_PATH): cv.All(
cv.file_,
cv.Any(cv.only_on(PLATFORM_HOST), cv.only_on_esp32),
cv.only_on(PLATFORM_HOST),
),
}
).extend(cv.COMPONENT_SCHEMA),
@@ -160,14 +160,7 @@ async def to_code(config):
cg.add(var.set_verify_ssl(config[CONF_VERIFY_SSL]))
if config.get(CONF_VERIFY_SSL):
if ca_cert_path := config.get(CONF_CA_CERTIFICATE_PATH):
with open(ca_cert_path, encoding="utf-8") as f:
ca_cert_content = f.read()
cg.add(var.set_ca_certificate(ca_cert_content))
else:
esp32.add_idf_sdkconfig_option(
"CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True
)
esp32.add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True)
esp32.add_idf_sdkconfig_option(
"CONFIG_ESP_TLS_INSECURE",

View File

@@ -27,9 +27,8 @@ void HttpRequestIDF::dump_config() {
HttpRequestComponent::dump_config();
ESP_LOGCONFIG(TAG,
" Buffer Size RX: %u\n"
" Buffer Size TX: %u\n"
" Custom CA Certificate: %s",
this->buffer_size_rx_, this->buffer_size_tx_, YESNO(this->ca_certificate_ != nullptr));
" Buffer Size TX: %u",
this->buffer_size_rx_, this->buffer_size_tx_);
}
esp_err_t HttpRequestIDF::http_event_handler(esp_http_client_event_t *evt) {
@@ -89,15 +88,11 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
config.disable_auto_redirect = !this->follow_redirects_;
config.max_redirection_count = this->redirect_limit_;
config.auth_type = HTTP_AUTH_TYPE_BASIC;
if (secure && this->verify_ssl_) {
if (this->ca_certificate_ != nullptr) {
config.cert_pem = this->ca_certificate_;
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
} else {
config.crt_bundle_attach = esp_crt_bundle_attach;
#endif
}
if (secure && this->verify_ssl_) {
config.crt_bundle_attach = esp_crt_bundle_attach;
}
#endif
if (this->useragent_ != nullptr) {
config.user_agent = this->useragent_;

View File

@@ -35,7 +35,6 @@ class HttpRequestIDF : public HttpRequestComponent {
void set_buffer_size_rx(uint16_t buffer_size_rx) { this->buffer_size_rx_ = buffer_size_rx; }
void set_buffer_size_tx(uint16_t buffer_size_tx) { this->buffer_size_tx_ = buffer_size_tx; }
void set_verify_ssl(bool verify_ssl) { this->verify_ssl_ = verify_ssl; }
void set_ca_certificate(const char *ca_certificate) { this->ca_certificate_ = ca_certificate; }
protected:
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
@@ -45,7 +44,6 @@ class HttpRequestIDF : public HttpRequestComponent {
uint16_t buffer_size_rx_{};
uint16_t buffer_size_tx_{};
bool verify_ssl_{true};
const char *ca_certificate_{nullptr};
/// @brief Monitors the http client events to gather response headers
static esp_err_t http_event_handler(esp_http_client_event_t *evt);

View File

@@ -82,7 +82,7 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
uint32_t last_progress = 0;
uint32_t update_start_time = millis();
md5::MD5Digest md5_receive;
char md5_receive_str[33];
std::unique_ptr<char[]> md5_receive_str(new char[33]);
if (this->md5_expected_.empty() && !this->http_get_md5_()) {
return OTA_MD5_INVALID;
@@ -176,14 +176,14 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
// verify MD5 is as expected and act accordingly
md5_receive.calculate();
md5_receive.get_hex(md5_receive_str);
this->md5_computed_ = md5_receive_str;
md5_receive.get_hex(md5_receive_str.get());
this->md5_computed_ = md5_receive_str.get();
if (strncmp(this->md5_computed_.c_str(), this->md5_expected_.c_str(), MD5_SIZE) != 0) {
ESP_LOGE(TAG, "MD5 computed: %s - Aborting due to MD5 mismatch", this->md5_computed_.c_str());
this->cleanup_(std::move(backend), container);
return ota::OTA_RESPONSE_ERROR_MD5_MISMATCH;
} else {
backend->set_update_md5(md5_receive_str);
backend->set_update_md5(md5_receive_str.get());
}
container->end();

View File

@@ -191,17 +191,10 @@ def _notify_old_style(config):
# The dev and latest branches will be at *least* this version, which is what matters.
# Use GitHub releases directly to avoid PlatformIO moderation delays.
ARDUINO_VERSIONS = {
"dev": (cv.Version(1, 11, 0), "https://github.com/libretiny-eu/libretiny.git"),
"latest": (
cv.Version(1, 11, 0),
"https://github.com/libretiny-eu/libretiny.git#v1.11.0",
),
"recommended": (
cv.Version(1, 11, 0),
"https://github.com/libretiny-eu/libretiny.git#v1.11.0",
),
"dev": (cv.Version(1, 9, 2), "https://github.com/libretiny-eu/libretiny.git"),
"latest": (cv.Version(1, 9, 2), "libretiny"),
"recommended": (cv.Version(1, 9, 2), None),
}

View File

@@ -7,7 +7,9 @@ namespace esphome::light {
class AddressableLightWrapper : public light::AddressableLight {
public:
explicit AddressableLightWrapper(light::LightState *light_state) : light_state_(light_state) {}
explicit AddressableLightWrapper(light::LightState *light_state) : light_state_(light_state) {
this->wrapper_state_ = new uint8_t[5]; // NOLINT(cppcoreguidelines-owning-memory)
}
int32_t size() const override { return 1; }
@@ -116,7 +118,7 @@ class AddressableLightWrapper : public light::AddressableLight {
}
light::LightState *light_state_;
mutable uint8_t wrapper_state_[5]{};
uint8_t *wrapper_state_;
ColorMode color_mode_{ColorMode::UNKNOWN};
};

View File

@@ -3,7 +3,8 @@
#include <cinttypes>
namespace esphome::mhz19 {
namespace esphome {
namespace mhz19 {
static const char *const TAG = "mhz19";
static const uint8_t MHZ19_REQUEST_LENGTH = 8;
@@ -16,19 +17,6 @@ static const uint8_t MHZ19_COMMAND_DETECTION_RANGE_0_2000PPM[] = {0xFF, 0x01, 0x
static const uint8_t MHZ19_COMMAND_DETECTION_RANGE_0_5000PPM[] = {0xFF, 0x01, 0x99, 0x00, 0x00, 0x00, 0x13, 0x88};
static const uint8_t MHZ19_COMMAND_DETECTION_RANGE_0_10000PPM[] = {0xFF, 0x01, 0x99, 0x00, 0x00, 0x00, 0x27, 0x10};
static const LogString *detection_range_to_log_string(MHZ19DetectionRange range) {
switch (range) {
case MHZ19_DETECTION_RANGE_0_2000PPM:
return LOG_STR("0-2000 ppm");
case MHZ19_DETECTION_RANGE_0_5000PPM:
return LOG_STR("0-5000 ppm");
case MHZ19_DETECTION_RANGE_0_10000PPM:
return LOG_STR("0-10000 ppm");
default:
return LOG_STR("default");
}
}
uint8_t mhz19_checksum(const uint8_t *command) {
uint8_t sum = 0;
for (uint8_t i = 1; i < MHZ19_REQUEST_LENGTH; i++) {
@@ -103,24 +91,24 @@ void MHZ19Component::abc_disable() {
this->mhz19_write_command_(MHZ19_COMMAND_ABC_DISABLE, nullptr);
}
void MHZ19Component::range_set(MHZ19DetectionRange detection_range) {
const uint8_t *command;
switch (detection_range) {
void MHZ19Component::range_set(MHZ19DetectionRange detection_ppm) {
switch (detection_ppm) {
case MHZ19_DETECTION_RANGE_DEFAULT:
ESP_LOGV(TAG, "Using previously set detection range (no change)");
break;
case MHZ19_DETECTION_RANGE_0_2000PPM:
command = MHZ19_COMMAND_DETECTION_RANGE_0_2000PPM;
ESP_LOGD(TAG, "Setting detection range to 0 to 2000ppm");
this->mhz19_write_command_(MHZ19_COMMAND_DETECTION_RANGE_0_2000PPM, nullptr);
break;
case MHZ19_DETECTION_RANGE_0_5000PPM:
command = MHZ19_COMMAND_DETECTION_RANGE_0_5000PPM;
ESP_LOGD(TAG, "Setting detection range to 0 to 5000ppm");
this->mhz19_write_command_(MHZ19_COMMAND_DETECTION_RANGE_0_5000PPM, nullptr);
break;
case MHZ19_DETECTION_RANGE_0_10000PPM:
command = MHZ19_COMMAND_DETECTION_RANGE_0_10000PPM;
ESP_LOGD(TAG, "Setting detection range to 0 to 10000ppm");
this->mhz19_write_command_(MHZ19_COMMAND_DETECTION_RANGE_0_10000PPM, nullptr);
break;
default:
ESP_LOGV(TAG, "Using previously set detection range (no change)");
return;
}
ESP_LOGD(TAG, "Setting detection range to %s", LOG_STR_ARG(detection_range_to_log_string(detection_range)));
this->mhz19_write_command_(command, nullptr);
}
bool MHZ19Component::mhz19_write_command_(const uint8_t *command, uint8_t *response) {
@@ -152,7 +140,24 @@ void MHZ19Component::dump_config() {
}
ESP_LOGCONFIG(TAG, " Warmup time: %" PRIu32 " s", this->warmup_seconds_);
ESP_LOGCONFIG(TAG, " Detection range: %s", LOG_STR_ARG(detection_range_to_log_string(this->detection_range_)));
const char *range_str;
switch (this->detection_range_) {
case MHZ19_DETECTION_RANGE_DEFAULT:
range_str = "default";
break;
case MHZ19_DETECTION_RANGE_0_2000PPM:
range_str = "0 to 2000ppm";
break;
case MHZ19_DETECTION_RANGE_0_5000PPM:
range_str = "0 to 5000ppm";
break;
case MHZ19_DETECTION_RANGE_0_10000PPM:
range_str = "0 to 10000ppm";
break;
}
ESP_LOGCONFIG(TAG, " Detection range: %s", range_str);
}
} // namespace esphome::mhz19
} // namespace mhz19
} // namespace esphome

View File

@@ -5,7 +5,8 @@
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h"
namespace esphome::mhz19 {
namespace esphome {
namespace mhz19 {
enum MHZ19ABCLogic {
MHZ19_ABC_NONE = 0,
@@ -31,7 +32,7 @@ class MHZ19Component : public PollingComponent, public uart::UARTDevice {
void calibrate_zero();
void abc_enable();
void abc_disable();
void range_set(MHZ19DetectionRange detection_range);
void range_set(MHZ19DetectionRange detection_ppm);
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
void set_co2_sensor(sensor::Sensor *co2_sensor) { co2_sensor_ = co2_sensor; }
@@ -73,4 +74,5 @@ template<typename... Ts> class MHZ19DetectionRangeSetAction : public Action<Ts..
void play(const Ts &...x) override { this->parent_->range_set(this->detection_range_.value(x...)); }
};
} // namespace esphome::mhz19
} // namespace mhz19
} // namespace esphome

View File

@@ -1,6 +1,5 @@
#include "mqtt_alarm_control_panel.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include "mqtt_const.h"
@@ -13,33 +12,6 @@ static const char *const TAG = "mqtt.alarm_control_panel";
using namespace esphome::alarm_control_panel;
static ProgmemStr alarm_state_to_mqtt_str(AlarmControlPanelState state) {
switch (state) {
case ACP_STATE_DISARMED:
return ESPHOME_F("disarmed");
case ACP_STATE_ARMED_HOME:
return ESPHOME_F("armed_home");
case ACP_STATE_ARMED_AWAY:
return ESPHOME_F("armed_away");
case ACP_STATE_ARMED_NIGHT:
return ESPHOME_F("armed_night");
case ACP_STATE_ARMED_VACATION:
return ESPHOME_F("armed_vacation");
case ACP_STATE_ARMED_CUSTOM_BYPASS:
return ESPHOME_F("armed_custom_bypass");
case ACP_STATE_PENDING:
return ESPHOME_F("pending");
case ACP_STATE_ARMING:
return ESPHOME_F("arming");
case ACP_STATE_DISARMING:
return ESPHOME_F("disarming");
case ACP_STATE_TRIGGERED:
return ESPHOME_F("triggered");
default:
return ESPHOME_F("unknown");
}
}
MQTTAlarmControlPanelComponent::MQTTAlarmControlPanelComponent(AlarmControlPanel *alarm_control_panel)
: alarm_control_panel_(alarm_control_panel) {}
void MQTTAlarmControlPanelComponent::setup() {
@@ -112,9 +84,42 @@ const EntityBase *MQTTAlarmControlPanelComponent::get_entity() const { return th
bool MQTTAlarmControlPanelComponent::send_initial_state() { return this->publish_state(); }
bool MQTTAlarmControlPanelComponent::publish_state() {
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
return this->publish(this->get_state_topic_to_(topic_buf),
alarm_state_to_mqtt_str(this->alarm_control_panel_->get_state()));
const char *state_s;
switch (this->alarm_control_panel_->get_state()) {
case ACP_STATE_DISARMED:
state_s = "disarmed";
break;
case ACP_STATE_ARMED_HOME:
state_s = "armed_home";
break;
case ACP_STATE_ARMED_AWAY:
state_s = "armed_away";
break;
case ACP_STATE_ARMED_NIGHT:
state_s = "armed_night";
break;
case ACP_STATE_ARMED_VACATION:
state_s = "armed_vacation";
break;
case ACP_STATE_ARMED_CUSTOM_BYPASS:
state_s = "armed_custom_bypass";
break;
case ACP_STATE_PENDING:
state_s = "pending";
break;
case ACP_STATE_ARMING:
state_s = "arming";
break;
case ACP_STATE_DISARMING:
state_s = "disarming";
break;
case ACP_STATE_TRIGGERED:
state_s = "triggered";
break;
default:
state_s = "unknown";
}
return this->publish(this->get_state_topic_(), state_s);
}
} // namespace esphome::mqtt

View File

@@ -52,9 +52,8 @@ bool MQTTBinarySensorComponent::publish_state(bool state) {
if (this->binary_sensor_->is_status_binary_sensor())
return true;
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
const char *state_s = state ? "ON" : "OFF";
return this->publish(this->get_state_topic_to_(topic_buf), state_s);
return this->publish(this->get_state_topic_(), state_s);
}
} // namespace esphome::mqtt

View File

@@ -1,6 +1,5 @@
#include "mqtt_climate.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include "mqtt_const.h"
@@ -13,111 +12,6 @@ static const char *const TAG = "mqtt.climate";
using namespace esphome::climate;
static ProgmemStr climate_mode_to_mqtt_str(ClimateMode mode) {
switch (mode) {
case CLIMATE_MODE_OFF:
return ESPHOME_F("off");
case CLIMATE_MODE_HEAT_COOL:
return ESPHOME_F("heat_cool");
case CLIMATE_MODE_AUTO:
return ESPHOME_F("auto");
case CLIMATE_MODE_COOL:
return ESPHOME_F("cool");
case CLIMATE_MODE_HEAT:
return ESPHOME_F("heat");
case CLIMATE_MODE_FAN_ONLY:
return ESPHOME_F("fan_only");
case CLIMATE_MODE_DRY:
return ESPHOME_F("dry");
default:
return ESPHOME_F("unknown");
}
}
static ProgmemStr climate_action_to_mqtt_str(ClimateAction action) {
switch (action) {
case CLIMATE_ACTION_OFF:
return ESPHOME_F("off");
case CLIMATE_ACTION_COOLING:
return ESPHOME_F("cooling");
case CLIMATE_ACTION_HEATING:
return ESPHOME_F("heating");
case CLIMATE_ACTION_IDLE:
return ESPHOME_F("idle");
case CLIMATE_ACTION_DRYING:
return ESPHOME_F("drying");
case CLIMATE_ACTION_FAN:
return ESPHOME_F("fan");
default:
return ESPHOME_F("unknown");
}
}
static ProgmemStr climate_fan_mode_to_mqtt_str(ClimateFanMode fan_mode) {
switch (fan_mode) {
case CLIMATE_FAN_ON:
return ESPHOME_F("on");
case CLIMATE_FAN_OFF:
return ESPHOME_F("off");
case CLIMATE_FAN_AUTO:
return ESPHOME_F("auto");
case CLIMATE_FAN_LOW:
return ESPHOME_F("low");
case CLIMATE_FAN_MEDIUM:
return ESPHOME_F("medium");
case CLIMATE_FAN_HIGH:
return ESPHOME_F("high");
case CLIMATE_FAN_MIDDLE:
return ESPHOME_F("middle");
case CLIMATE_FAN_FOCUS:
return ESPHOME_F("focus");
case CLIMATE_FAN_DIFFUSE:
return ESPHOME_F("diffuse");
case CLIMATE_FAN_QUIET:
return ESPHOME_F("quiet");
default:
return ESPHOME_F("unknown");
}
}
static ProgmemStr climate_swing_mode_to_mqtt_str(ClimateSwingMode swing_mode) {
switch (swing_mode) {
case CLIMATE_SWING_OFF:
return ESPHOME_F("off");
case CLIMATE_SWING_BOTH:
return ESPHOME_F("both");
case CLIMATE_SWING_VERTICAL:
return ESPHOME_F("vertical");
case CLIMATE_SWING_HORIZONTAL:
return ESPHOME_F("horizontal");
default:
return ESPHOME_F("unknown");
}
}
static ProgmemStr climate_preset_to_mqtt_str(ClimatePreset preset) {
switch (preset) {
case CLIMATE_PRESET_NONE:
return ESPHOME_F("none");
case CLIMATE_PRESET_HOME:
return ESPHOME_F("home");
case CLIMATE_PRESET_ECO:
return ESPHOME_F("eco");
case CLIMATE_PRESET_AWAY:
return ESPHOME_F("away");
case CLIMATE_PRESET_BOOST:
return ESPHOME_F("boost");
case CLIMATE_PRESET_COMFORT:
return ESPHOME_F("comfort");
case CLIMATE_PRESET_SLEEP:
return ESPHOME_F("sleep");
case CLIMATE_PRESET_ACTIVITY:
return ESPHOME_F("activity");
default:
return ESPHOME_F("unknown");
}
}
void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
auto traits = this->device_->get_traits();
@@ -366,8 +260,34 @@ const EntityBase *MQTTClimateComponent::get_entity() const { return this->device
bool MQTTClimateComponent::publish_state_() {
auto traits = this->device_->get_traits();
// mode
const char *mode_s;
switch (this->device_->mode) {
case CLIMATE_MODE_OFF:
mode_s = "off";
break;
case CLIMATE_MODE_AUTO:
mode_s = "auto";
break;
case CLIMATE_MODE_COOL:
mode_s = "cool";
break;
case CLIMATE_MODE_HEAT:
mode_s = "heat";
break;
case CLIMATE_MODE_FAN_ONLY:
mode_s = "fan_only";
break;
case CLIMATE_MODE_DRY:
mode_s = "dry";
break;
case CLIMATE_MODE_HEAT_COOL:
mode_s = "heat_cool";
break;
default:
mode_s = "unknown";
}
bool success = true;
if (!this->publish(this->get_mode_state_topic(), climate_mode_to_mqtt_str(this->device_->mode)))
if (!this->publish(this->get_mode_state_topic(), mode_s))
success = false;
int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals();
int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals();
@@ -407,37 +327,134 @@ bool MQTTClimateComponent::publish_state_() {
}
if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
if (this->device_->has_custom_preset()) {
if (!this->publish(this->get_preset_state_topic(), this->device_->get_custom_preset()))
success = false;
} else if (this->device_->preset.has_value()) {
if (!this->publish(this->get_preset_state_topic(), climate_preset_to_mqtt_str(this->device_->preset.value())))
success = false;
} else if (!this->publish(this->get_preset_state_topic(), "")) {
success = false;
std::string payload;
if (this->device_->preset.has_value()) {
switch (this->device_->preset.value()) {
case CLIMATE_PRESET_NONE:
payload = "none";
break;
case CLIMATE_PRESET_HOME:
payload = "home";
break;
case CLIMATE_PRESET_AWAY:
payload = "away";
break;
case CLIMATE_PRESET_BOOST:
payload = "boost";
break;
case CLIMATE_PRESET_COMFORT:
payload = "comfort";
break;
case CLIMATE_PRESET_ECO:
payload = "eco";
break;
case CLIMATE_PRESET_SLEEP:
payload = "sleep";
break;
case CLIMATE_PRESET_ACTIVITY:
payload = "activity";
break;
default:
payload = "unknown";
}
}
if (this->device_->has_custom_preset())
payload = this->device_->get_custom_preset().c_str();
if (!this->publish(this->get_preset_state_topic(), payload))
success = false;
}
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION)) {
if (!this->publish(this->get_action_state_topic(), climate_action_to_mqtt_str(this->device_->action)))
const char *payload;
switch (this->device_->action) {
case CLIMATE_ACTION_OFF:
payload = "off";
break;
case CLIMATE_ACTION_COOLING:
payload = "cooling";
break;
case CLIMATE_ACTION_HEATING:
payload = "heating";
break;
case CLIMATE_ACTION_IDLE:
payload = "idle";
break;
case CLIMATE_ACTION_DRYING:
payload = "drying";
break;
case CLIMATE_ACTION_FAN:
payload = "fan";
break;
default:
payload = "unknown";
}
if (!this->publish(this->get_action_state_topic(), payload))
success = false;
}
if (traits.get_supports_fan_modes()) {
if (this->device_->has_custom_fan_mode()) {
if (!this->publish(this->get_fan_mode_state_topic(), this->device_->get_custom_fan_mode()))
success = false;
} else if (this->device_->fan_mode.has_value()) {
if (!this->publish(this->get_fan_mode_state_topic(),
climate_fan_mode_to_mqtt_str(this->device_->fan_mode.value())))
success = false;
} else if (!this->publish(this->get_fan_mode_state_topic(), "")) {
success = false;
std::string payload;
if (this->device_->fan_mode.has_value()) {
switch (this->device_->fan_mode.value()) {
case CLIMATE_FAN_ON:
payload = "on";
break;
case CLIMATE_FAN_OFF:
payload = "off";
break;
case CLIMATE_FAN_AUTO:
payload = "auto";
break;
case CLIMATE_FAN_LOW:
payload = "low";
break;
case CLIMATE_FAN_MEDIUM:
payload = "medium";
break;
case CLIMATE_FAN_HIGH:
payload = "high";
break;
case CLIMATE_FAN_MIDDLE:
payload = "middle";
break;
case CLIMATE_FAN_FOCUS:
payload = "focus";
break;
case CLIMATE_FAN_DIFFUSE:
payload = "diffuse";
break;
case CLIMATE_FAN_QUIET:
payload = "quiet";
break;
default:
payload = "unknown";
}
}
if (this->device_->has_custom_fan_mode())
payload = this->device_->get_custom_fan_mode().c_str();
if (!this->publish(this->get_fan_mode_state_topic(), payload))
success = false;
}
if (traits.get_supports_swing_modes()) {
if (!this->publish(this->get_swing_mode_state_topic(), climate_swing_mode_to_mqtt_str(this->device_->swing_mode)))
const char *payload;
switch (this->device_->swing_mode) {
case CLIMATE_SWING_OFF:
payload = "off";
break;
case CLIMATE_SWING_BOTH:
payload = "both";
break;
case CLIMATE_SWING_VERTICAL:
payload = "vertical";
break;
case CLIMATE_SWING_HORIZONTAL:
payload = "horizontal";
break;
default:
payload = "unknown";
}
if (!this->publish(this->get_swing_mode_state_topic(), payload))
success = false;
}

View File

@@ -5,7 +5,6 @@
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include "esphome/core/version.h"
#include "mqtt_const.h"
@@ -133,45 +132,17 @@ std::string MQTTComponent::get_command_topic_() const {
}
bool MQTTComponent::publish(const std::string &topic, const std::string &payload) {
return this->publish(topic.c_str(), payload.data(), payload.size());
return this->publish(topic, payload.data(), payload.size());
}
bool MQTTComponent::publish(const std::string &topic, const char *payload, size_t payload_length) {
return this->publish(topic.c_str(), payload, payload_length);
}
bool MQTTComponent::publish(const char *topic, const char *payload, size_t payload_length) {
if (topic[0] == '\0')
if (topic.empty())
return false;
return global_mqtt_client->publish(topic, payload, payload_length, this->qos_, this->retain_);
}
bool MQTTComponent::publish(const char *topic, const char *payload) {
return this->publish(topic, payload, strlen(payload));
}
#ifdef USE_ESP8266
bool MQTTComponent::publish(const std::string &topic, ProgmemStr payload) {
return this->publish(topic.c_str(), payload);
}
bool MQTTComponent::publish(const char *topic, ProgmemStr payload) {
if (topic[0] == '\0')
return false;
// On ESP8266, ProgmemStr is __FlashStringHelper* - need to copy from flash
char buf[64];
strncpy_P(buf, reinterpret_cast<const char *>(payload), sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
return global_mqtt_client->publish(topic, buf, strlen(buf), this->qos_, this->retain_);
}
#endif
bool MQTTComponent::publish_json(const std::string &topic, const json::json_build_t &f) {
return this->publish_json(topic.c_str(), f);
}
bool MQTTComponent::publish_json(const char *topic, const json::json_build_t &f) {
if (topic[0] == '\0')
if (topic.empty())
return false;
return global_mqtt_client->publish_json(topic, f, this->qos_, this->retain_);
}

View File

@@ -9,7 +9,6 @@
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/progmem.h"
#include "esphome/core/string_ref.h"
#include "mqtt_client.h"
@@ -158,70 +157,6 @@ class MQTTComponent : public Component {
*/
bool publish(const std::string &topic, const char *payload, size_t payload_length);
/** Send a MQTT message.
*
* @param topic The topic.
* @param payload The null-terminated payload.
*/
bool publish(const std::string &topic, const char *payload) {
return this->publish(topic.c_str(), payload, strlen(payload));
}
/** Send a MQTT message (no heap allocation for topic).
*
* @param topic The topic as C string.
* @param payload The payload buffer.
* @param payload_length The length of the payload.
*/
bool publish(const char *topic, const char *payload, size_t payload_length);
/** Send a MQTT message (no heap allocation for topic).
*
* @param topic The topic as StringRef (for use with get_state_topic_to_()).
* @param payload The payload buffer.
* @param payload_length The length of the payload.
*/
bool publish(StringRef topic, const char *payload, size_t payload_length) {
return this->publish(topic.c_str(), payload, payload_length);
}
/** Send a MQTT message (no heap allocation for topic).
*
* @param topic The topic as C string.
* @param payload The null-terminated payload.
*/
bool publish(const char *topic, const char *payload);
/** Send a MQTT message (no heap allocation for topic).
*
* @param topic The topic as StringRef (for use with get_state_topic_to_()).
* @param payload The null-terminated payload.
*/
bool publish(StringRef topic, const char *payload) { return this->publish(topic.c_str(), payload); }
#ifdef USE_ESP8266
/** Send a MQTT message with a PROGMEM string payload.
*
* @param topic The topic.
* @param payload The payload (ProgmemStr - stored in flash on ESP8266).
*/
bool publish(const std::string &topic, ProgmemStr payload);
/** Send a MQTT message with a PROGMEM string payload (no heap allocation for topic).
*
* @param topic The topic as C string.
* @param payload The payload (ProgmemStr - stored in flash on ESP8266).
*/
bool publish(const char *topic, ProgmemStr payload);
/** Send a MQTT message with a PROGMEM string payload (no heap allocation for topic).
*
* @param topic The topic as StringRef (for use with get_state_topic_to_()).
* @param payload The payload (ProgmemStr - stored in flash on ESP8266).
*/
bool publish(StringRef topic, ProgmemStr payload) { return this->publish(topic.c_str(), payload); }
#endif
/** Construct and send a JSON MQTT message.
*
* @param topic The topic.
@@ -229,20 +164,6 @@ class MQTTComponent : public Component {
*/
bool publish_json(const std::string &topic, const json::json_build_t &f);
/** Construct and send a JSON MQTT message (no heap allocation for topic).
*
* @param topic The topic as C string.
* @param f The Json Message builder.
*/
bool publish_json(const char *topic, const json::json_build_t &f);
/** Construct and send a JSON MQTT message (no heap allocation for topic).
*
* @param topic The topic as StringRef (for use with get_state_topic_to_()).
* @param f The Json Message builder.
*/
bool publish_json(StringRef topic, const json::json_build_t &f) { return this->publish_json(topic.c_str(), f); }
/** Subscribe to a MQTT topic.
*
* @param topic The topic. Wildcards are currently not supported.

View File

@@ -1,6 +1,5 @@
#include "mqtt_cover.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include "mqtt_const.h"
@@ -13,20 +12,6 @@ static const char *const TAG = "mqtt.cover";
using namespace esphome::cover;
static ProgmemStr cover_state_to_mqtt_str(CoverOperation operation, float position, bool supports_position) {
if (operation == COVER_OPERATION_OPENING)
return ESPHOME_F("opening");
if (operation == COVER_OPERATION_CLOSING)
return ESPHOME_F("closing");
if (position == COVER_CLOSED)
return ESPHOME_F("closed");
if (position == COVER_OPEN)
return ESPHOME_F("open");
if (supports_position)
return ESPHOME_F("open");
return ESPHOME_F("unknown");
}
MQTTCoverComponent::MQTTCoverComponent(Cover *cover) : cover_(cover) {}
void MQTTCoverComponent::setup() {
auto traits = this->cover_->get_traits();
@@ -124,10 +109,13 @@ bool MQTTCoverComponent::publish_state() {
if (!this->publish(this->get_tilt_state_topic(), pos, len))
success = false;
}
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
if (!this->publish(this->get_state_topic_to_(topic_buf),
cover_state_to_mqtt_str(this->cover_->current_operation, this->cover_->position,
traits.get_supports_position())))
const char *state_s = this->cover_->current_operation == COVER_OPERATION_OPENING ? "opening"
: this->cover_->current_operation == COVER_OPERATION_CLOSING ? "closing"
: this->cover_->position == COVER_CLOSED ? "closed"
: this->cover_->position == COVER_OPEN ? "open"
: traits.get_supports_position() ? "open"
: "unknown";
if (!this->publish(this->get_state_topic_(), state_s))
success = false;
return success;
}

View File

@@ -53,8 +53,7 @@ bool MQTTDateComponent::send_initial_state() {
}
}
bool MQTTDateComponent::publish_state(uint16_t year, uint8_t month, uint8_t day) {
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
return this->publish_json(this->get_state_topic_to_(topic_buf), [year, month, day](JsonObject root) {
return this->publish_json(this->get_state_topic_(), [year, month, day](JsonObject root) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
root[ESPHOME_F("year")] = year;
root[ESPHOME_F("month")] = month;

View File

@@ -66,17 +66,15 @@ bool MQTTDateTimeComponent::send_initial_state() {
}
bool MQTTDateTimeComponent::publish_state(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute,
uint8_t second) {
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
return this->publish_json(this->get_state_topic_to_(topic_buf),
[year, month, day, hour, minute, second](JsonObject root) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
root[ESPHOME_F("year")] = year;
root[ESPHOME_F("month")] = month;
root[ESPHOME_F("day")] = day;
root[ESPHOME_F("hour")] = hour;
root[ESPHOME_F("minute")] = minute;
root[ESPHOME_F("second")] = second;
});
return this->publish_json(this->get_state_topic_(), [year, month, day, hour, minute, second](JsonObject root) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
root[ESPHOME_F("year")] = year;
root[ESPHOME_F("month")] = month;
root[ESPHOME_F("day")] = day;
root[ESPHOME_F("hour")] = hour;
root[ESPHOME_F("minute")] = minute;
root[ESPHOME_F("second")] = second;
});
}
} // namespace esphome::mqtt

View File

@@ -44,8 +44,7 @@ void MQTTEventComponent::dump_config() {
}
bool MQTTEventComponent::publish_event_(const std::string &event_type) {
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
return this->publish_json(this->get_state_topic_to_(topic_buf), [event_type](JsonObject root) {
return this->publish_json(this->get_state_topic_(), [event_type](JsonObject root) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
root[MQTT_EVENT_TYPE] = event_type;
});

View File

@@ -1,6 +1,5 @@
#include "mqtt_fan.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include "mqtt_const.h"
@@ -13,14 +12,6 @@ static const char *const TAG = "mqtt.fan";
using namespace esphome::fan;
static ProgmemStr fan_direction_to_mqtt_str(FanDirection direction) {
return direction == FanDirection::FORWARD ? ESPHOME_F("forward") : ESPHOME_F("reverse");
}
static ProgmemStr fan_oscillation_to_mqtt_str(bool oscillating) {
return oscillating ? ESPHOME_F("oscillate_on") : ESPHOME_F("oscillate_off");
}
MQTTFanComponent::MQTTFanComponent(Fan *state) : state_(state) {}
Fan *MQTTFanComponent::get_state() const { return this->state_; }
@@ -167,18 +158,18 @@ void MQTTFanComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig
}
}
bool MQTTFanComponent::publish_state() {
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
const char *state_s = this->state_->state ? "ON" : "OFF";
ESP_LOGD(TAG, "'%s' Sending state %s.", this->state_->get_name().c_str(), state_s);
this->publish(this->get_state_topic_to_(topic_buf), state_s);
this->publish(this->get_state_topic_(), state_s);
bool failed = false;
if (this->state_->get_traits().supports_direction()) {
bool success = this->publish(this->get_direction_state_topic(), fan_direction_to_mqtt_str(this->state_->direction));
bool success = this->publish(this->get_direction_state_topic(),
this->state_->direction == fan::FanDirection::FORWARD ? "forward" : "reverse");
failed = failed || !success;
}
if (this->state_->get_traits().supports_oscillation()) {
bool success =
this->publish(this->get_oscillation_state_topic(), fan_oscillation_to_mqtt_str(this->state_->oscillating));
bool success = this->publish(this->get_oscillation_state_topic(),
this->state_->oscillating ? "oscillate_on" : "oscillate_off");
failed = failed || !success;
}
auto traits = this->state_->get_traits();

View File

@@ -34,8 +34,7 @@ void MQTTJSONLightComponent::on_light_remote_values_update() {
MQTTJSONLightComponent::MQTTJSONLightComponent(LightState *state) : state_(state) {}
bool MQTTJSONLightComponent::publish_state_() {
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
return this->publish_json(this->get_state_topic_to_(topic_buf), [this](JsonObject root) {
return this->publish_json(this->get_state_topic_(), [this](JsonObject root) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
LightJSONSchema::dump_json(*this->state_, root);
});

View File

@@ -47,14 +47,13 @@ void MQTTLockComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfi
bool MQTTLockComponent::send_initial_state() { return this->publish_state(); }
bool MQTTLockComponent::publish_state() {
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
#ifdef USE_STORE_LOG_STR_IN_FLASH
char buf[LOCK_STATE_STR_SIZE];
strncpy_P(buf, (PGM_P) lock_state_to_string(this->lock_->state), sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
return this->publish(this->get_state_topic_to_(topic_buf), buf);
return this->publish(this->get_state_topic_(), buf);
#else
return this->publish(this->get_state_topic_to_(topic_buf), LOG_STR_ARG(lock_state_to_string(this->lock_->state)));
return this->publish(this->get_state_topic_(), LOG_STR_ARG(lock_state_to_string(this->lock_->state)));
#endif
}

View File

@@ -74,10 +74,9 @@ bool MQTTNumberComponent::send_initial_state() {
}
}
bool MQTTNumberComponent::publish_state(float value) {
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
char buffer[64];
size_t len = buf_append_printf(buffer, sizeof(buffer), 0, "%f", value);
return this->publish(this->get_state_topic_to_(topic_buf), buffer, len);
buf_append_printf(buffer, sizeof(buffer), 0, "%f", value);
return this->publish(this->get_state_topic_(), buffer);
}
} // namespace esphome::mqtt

View File

@@ -50,8 +50,7 @@ bool MQTTSelectComponent::send_initial_state() {
}
}
bool MQTTSelectComponent::publish_state(const std::string &value) {
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
return this->publish(this->get_state_topic_to_(topic_buf), value.data(), value.size());
return this->publish(this->get_state_topic_(), value);
}
} // namespace esphome::mqtt

View File

@@ -79,13 +79,12 @@ bool MQTTSensorComponent::send_initial_state() {
}
}
bool MQTTSensorComponent::publish_state(float value) {
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
if (mqtt::global_mqtt_client->is_publish_nan_as_none() && std::isnan(value))
return this->publish(this->get_state_topic_to_(topic_buf), "None", 4);
return this->publish(this->get_state_topic_(), "None", 4);
int8_t accuracy = this->sensor_->get_accuracy_decimals();
char buf[VALUE_ACCURACY_MAX_LEN];
size_t len = value_accuracy_to_buf(buf, value, accuracy);
return this->publish(this->get_state_topic_to_(topic_buf), buf, len);
return this->publish(this->get_state_topic_(), buf, len);
}
} // namespace esphome::mqtt

View File

@@ -52,9 +52,8 @@ void MQTTSwitchComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon
bool MQTTSwitchComponent::send_initial_state() { return this->publish_state(this->switch_->state); }
bool MQTTSwitchComponent::publish_state(bool state) {
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
const char *state_s = state ? "ON" : "OFF";
return this->publish(this->get_state_topic_to_(topic_buf), state_s);
return this->publish(this->get_state_topic_(), state_s);
}
} // namespace esphome::mqtt

View File

@@ -53,8 +53,7 @@ bool MQTTTextComponent::send_initial_state() {
}
}
bool MQTTTextComponent::publish_state(const std::string &value) {
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
return this->publish(this->get_state_topic_to_(topic_buf), value.data(), value.size());
return this->publish(this->get_state_topic_(), value);
}
} // namespace esphome::mqtt

View File

@@ -31,10 +31,7 @@ void MQTTTextSensor::dump_config() {
LOG_MQTT_COMPONENT(true, false);
}
bool MQTTTextSensor::publish_state(const std::string &value) {
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
return this->publish(this->get_state_topic_to_(topic_buf), value.data(), value.size());
}
bool MQTTTextSensor::publish_state(const std::string &value) { return this->publish(this->get_state_topic_(), value); }
bool MQTTTextSensor::send_initial_state() {
if (this->sensor_->has_state()) {
return this->publish_state(this->sensor_->state);

View File

@@ -53,8 +53,7 @@ bool MQTTTimeComponent::send_initial_state() {
}
}
bool MQTTTimeComponent::publish_state(uint8_t hour, uint8_t minute, uint8_t second) {
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
return this->publish_json(this->get_state_topic_to_(topic_buf), [hour, minute, second](JsonObject root) {
return this->publish_json(this->get_state_topic_(), [hour, minute, second](JsonObject root) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
root[ESPHOME_F("hour")] = hour;
root[ESPHOME_F("minute")] = minute;

View File

@@ -28,8 +28,7 @@ void MQTTUpdateComponent::setup() {
}
bool MQTTUpdateComponent::publish_state() {
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
return this->publish_json(this->get_state_topic_to_(topic_buf), [this](JsonObject root) {
return this->publish_json(this->get_state_topic_(), [this](JsonObject root) {
root[ESPHOME_F("installed_version")] = this->update_->update_info.current_version;
root[ESPHOME_F("latest_version")] = this->update_->update_info.latest_version;
root[ESPHOME_F("title")] = this->update_->update_info.title;

View File

@@ -1,6 +1,5 @@
#include "mqtt_valve.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include "mqtt_const.h"
@@ -13,20 +12,6 @@ static const char *const TAG = "mqtt.valve";
using namespace esphome::valve;
static ProgmemStr valve_state_to_mqtt_str(ValveOperation operation, float position, bool supports_position) {
if (operation == VALVE_OPERATION_OPENING)
return ESPHOME_F("opening");
if (operation == VALVE_OPERATION_CLOSING)
return ESPHOME_F("closing");
if (position == VALVE_CLOSED)
return ESPHOME_F("closed");
if (position == VALVE_OPEN)
return ESPHOME_F("open");
if (supports_position)
return ESPHOME_F("open");
return ESPHOME_F("unknown");
}
MQTTValveComponent::MQTTValveComponent(Valve *valve) : valve_(valve) {}
void MQTTValveComponent::setup() {
auto traits = this->valve_->get_traits();
@@ -93,10 +78,13 @@ bool MQTTValveComponent::publish_state() {
if (!this->publish(this->get_position_state_topic(), pos, len))
success = false;
}
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
if (!this->publish(this->get_state_topic_to_(topic_buf),
valve_state_to_mqtt_str(this->valve_->current_operation, this->valve_->position,
traits.get_supports_position())))
const char *state_s = this->valve_->current_operation == VALVE_OPERATION_OPENING ? "opening"
: this->valve_->current_operation == VALVE_OPERATION_CLOSING ? "closing"
: this->valve_->position == VALVE_CLOSED ? "closed"
: this->valve_->position == VALVE_OPEN ? "open"
: traits.get_supports_position() ? "open"
: "unknown";
if (!this->publish(this->get_state_topic_(), state_s))
success = false;
return success;
}

View File

@@ -1,6 +1,5 @@
#pragma once
#include <array>
#include <cstdint>
namespace esphome {
@@ -22,7 +21,7 @@ enum SmlMessageType : uint16_t { SML_PUBLIC_OPEN_RES = 0x0101, SML_GET_LIST_RES
const uint16_t START_MASK = 0x55aa; // 0x1b 1b 1b 1b 01 01 01 01
const uint16_t END_MASK = 0x0157; // 0x1b 1b 1b 1b 1a
constexpr std::array<uint8_t, 8> START_SEQ = {0x1b, 0x1b, 0x1b, 0x1b, 0x01, 0x01, 0x01, 0x01};
const std::vector<uint8_t> START_SEQ = {0x1b, 0x1b, 0x1b, 0x1b, 0x01, 0x01, 0x01, 0x01};
} // namespace sml
} // namespace esphome

View File

@@ -20,7 +20,7 @@ from .. import template_ns
CONF_CURRENT_TEMPERATURE = "current_temperature"
TemplateWaterHeater = template_ns.class_(
"TemplateWaterHeater", cg.Component, water_heater.WaterHeater
"TemplateWaterHeater", water_heater.WaterHeater, cg.Component
)
TemplateWaterHeaterPublishAction = template_ns.class_(

View File

@@ -35,6 +35,9 @@ void TM1638Component::setup() {
this->set_intensity(intensity_);
this->reset_(); // all LEDs off
for (uint8_t i = 0; i < 8; i++) // zero fill print buffer
this->buffer_[i] = 0;
}
void TM1638Component::dump_config() {

View File

@@ -70,7 +70,7 @@ class TM1638Component : public PollingComponent {
GPIOPin *clk_pin_;
GPIOPin *stb_pin_;
GPIOPin *dio_pin_;
uint8_t buffer_[8]{};
uint8_t *buffer_ = new uint8_t[8];
tm1638_writer_t writer_{};
std::vector<KeyListener *> listeners_{};
};

View File

@@ -1,23 +1,11 @@
#include "uln2003.h"
#include "esphome/core/log.h"
namespace esphome::uln2003 {
namespace esphome {
namespace uln2003 {
static const char *const TAG = "uln2003.stepper";
static const LogString *step_mode_to_log_string(ULN2003StepMode mode) {
switch (mode) {
case ULN2003_STEP_MODE_FULL_STEP:
return LOG_STR("FULL STEP");
case ULN2003_STEP_MODE_HALF_STEP:
return LOG_STR("HALF STEP");
case ULN2003_STEP_MODE_WAVE_DRIVE:
return LOG_STR("WAVE DRIVE");
default:
return LOG_STR("UNKNOWN");
}
}
void ULN2003::setup() {
this->pin_a_->setup();
this->pin_b_->setup();
@@ -54,7 +42,22 @@ void ULN2003::dump_config() {
LOG_PIN(" Pin B: ", this->pin_b_);
LOG_PIN(" Pin C: ", this->pin_c_);
LOG_PIN(" Pin D: ", this->pin_d_);
ESP_LOGCONFIG(TAG, " Step Mode: %s", LOG_STR_ARG(step_mode_to_log_string(this->step_mode_)));
const char *step_mode_s;
switch (this->step_mode_) {
case ULN2003_STEP_MODE_FULL_STEP:
step_mode_s = "FULL STEP";
break;
case ULN2003_STEP_MODE_HALF_STEP:
step_mode_s = "HALF STEP";
break;
case ULN2003_STEP_MODE_WAVE_DRIVE:
step_mode_s = "WAVE DRIVE";
break;
default:
step_mode_s = "UNKNOWN";
break;
}
ESP_LOGCONFIG(TAG, " Step Mode: %s", step_mode_s);
}
void ULN2003::write_step_(int32_t step) {
int32_t n = this->step_mode_ == ULN2003_STEP_MODE_HALF_STEP ? 8 : 4;
@@ -87,4 +90,5 @@ void ULN2003::write_step_(int32_t step) {
this->pin_d_->digital_write((res >> 3) & 1);
}
} // namespace esphome::uln2003
} // namespace uln2003
} // namespace esphome

View File

@@ -4,7 +4,8 @@
#include "esphome/core/hal.h"
#include "esphome/components/stepper/stepper.h"
namespace esphome::uln2003 {
namespace esphome {
namespace uln2003 {
enum ULN2003StepMode {
ULN2003_STEP_MODE_FULL_STEP,
@@ -39,4 +40,5 @@ class ULN2003 : public stepper::Stepper, public Component {
int32_t current_uln_pos_{0};
};
} // namespace esphome::uln2003
} // namespace uln2003
} // namespace esphome

View File

@@ -2,8 +2,6 @@
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include <strings.h>
namespace esphome {
@@ -40,13 +38,13 @@ Valve::Valve() : position{VALVE_OPEN} {}
ValveCall::ValveCall(Valve *parent) : parent_(parent) {}
ValveCall &ValveCall::set_command(const char *command) {
if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("OPEN")) == 0) {
if (strcasecmp(command, "OPEN") == 0) {
this->set_command_open();
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("CLOSE")) == 0) {
} else if (strcasecmp(command, "CLOSE") == 0) {
this->set_command_close();
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("STOP")) == 0) {
} else if (strcasecmp(command, "STOP") == 0) {
this->set_command_stop();
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("TOGGLE")) == 0) {
} else if (strcasecmp(command, "TOGGLE") == 0) {
this->set_command_toggle();
} else {
ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command);

View File

@@ -698,10 +698,6 @@ bool WiFiComponent::wifi_scan_start_(bool passive) {
if (!this->wifi_mode_(true, {}))
return false;
// Reset scan_done_ before starting new scan to prevent stale flag from previous scan
// (e.g., roaming scan completed just before unexpected disconnect)
this->scan_done_ = false;
struct scan_config config {};
memset(&config, 0, sizeof(config));
config.ssid = nullptr;

View File

@@ -14,7 +14,6 @@
#include <algorithm>
#include <cinttypes>
#include <memory>
#include <utility>
#ifdef USE_WIFI_WPA2_EAP
#if (ESP_IDF_VERSION_MAJOR >= 5) && (ESP_IDF_VERSION_MINOR >= 1)
@@ -829,29 +828,16 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
uint16_t number = it.number;
scan_result_.init(number);
#ifdef USE_ESP32_HOSTED
// getting records one at a time fails on P4 with hosted esp32 WiFi coprocessor
// Presumably an upstream bug, work-around by getting all records at once
auto records = std::make_unique<wifi_ap_record_t[]>(number);
err = esp_wifi_scan_get_ap_records(&number, records.get());
if (err != ESP_OK) {
esp_wifi_clear_ap_list();
ESP_LOGW(TAG, "esp_wifi_scan_get_ap_records failed: %s", esp_err_to_name(err));
return;
}
for (uint16_t i = 0; i < number; i++) {
wifi_ap_record_t &record = records[i];
#else
// Process one record at a time to avoid large buffer allocation
wifi_ap_record_t record;
for (uint16_t i = 0; i < number; i++) {
wifi_ap_record_t record;
err = esp_wifi_scan_get_ap_record(&record);
if (err != ESP_OK) {
ESP_LOGW(TAG, "esp_wifi_scan_get_ap_record failed: %s", esp_err_to_name(err));
esp_wifi_clear_ap_list(); // Free remaining records not yet retrieved
break;
}
#endif // USE_ESP32_HOSTED
bssid_t bssid;
std::copy(record.bssid, record.bssid + 6, bssid.begin());
std::string ssid(reinterpret_cast<const char *>(record.ssid));

View File

@@ -649,10 +649,6 @@ bool WiFiComponent::wifi_scan_start_(bool passive) {
if (!this->wifi_mode_(true, {}))
return false;
// Reset scan_done_ before starting new scan to prevent stale flag from previous scan
// (e.g., roaming scan completed just before unexpected disconnect)
this->scan_done_ = false;
// need to use WiFi because of WiFiScanClass allocations :(
int16_t err = WiFi.scanNetworks(true, true, passive, 200);
if (err != WIFI_SCAN_RUNNING) {

View File

@@ -42,7 +42,6 @@
#define USE_DEVICES
#define USE_DISPLAY
#define USE_ENTITY_ICON
#define USE_ESP32_HOSTED
#define USE_ESP32_IMPROV_STATE_CALLBACK
#define USE_EVENT
#define USE_FAN

View File

@@ -3,7 +3,6 @@
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include "esphome/core/string_ref.h"
#include <strings.h>
@@ -452,15 +451,15 @@ std::string format_bin(const uint8_t *data, size_t length) {
}
ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) {
if (on == nullptr && ESPHOME_strcasecmp_P(str, ESPHOME_PSTR("on")) == 0)
if (on == nullptr && strcasecmp(str, "on") == 0)
return PARSE_ON;
if (on != nullptr && strcasecmp(str, on) == 0)
return PARSE_ON;
if (off == nullptr && ESPHOME_strcasecmp_P(str, ESPHOME_PSTR("off")) == 0)
if (off == nullptr && strcasecmp(str, "off") == 0)
return PARSE_OFF;
if (off != nullptr && strcasecmp(str, off) == 0)
return PARSE_OFF;
if (ESPHOME_strcasecmp_P(str, ESPHOME_PSTR("toggle")) == 0)
if (strcasecmp(str, "toggle") == 0)
return PARSE_TOGGLE;
return PARSE_NONE;

View File

@@ -12,10 +12,6 @@
#define ESPHOME_strncpy_P strncpy_P
#define ESPHOME_strncat_P strncat_P
#define ESPHOME_snprintf_P snprintf_P
#define ESPHOME_strcmp_P strcmp_P
#define ESPHOME_strcasecmp_P strcasecmp_P
#define ESPHOME_strncmp_P strncmp_P
#define ESPHOME_strncasecmp_P strncasecmp_P
// Type for pointers to PROGMEM strings (for use with ESPHOME_F return values)
using ProgmemStr = const __FlashStringHelper *;
#else
@@ -25,10 +21,6 @@ using ProgmemStr = const __FlashStringHelper *;
#define ESPHOME_strncpy_P strncpy
#define ESPHOME_strncat_P strncat
#define ESPHOME_snprintf_P snprintf
#define ESPHOME_strcmp_P strcmp
#define ESPHOME_strcasecmp_P strcasecmp
#define ESPHOME_strncmp_P strncmp
#define ESPHOME_strncasecmp_P strncasecmp
// Type for pointers to strings (no PROGMEM on non-ESP8266 platforms)
using ProgmemStr = const char *;
#endif

View File

@@ -154,12 +154,6 @@ def check_error(data: list[int] | bytes, expect: int | list[int] | None) -> None
"""
if not expect:
return
if not data:
raise OTAError(
"Error: Device closed connection without responding. "
"This may indicate the device ran out of memory, "
"a network issue, or the connection was interrupted."
)
dat = data[0]
if dat == RESPONSE_ERROR_MAGIC:
raise OTAError("Error: Invalid magic byte")

View File

@@ -212,7 +212,7 @@ build_unflags =
; This are common settings for the LibreTiny (all variants) using Arduino.
[common:libretiny-arduino]
extends = common:arduino
platform = https://github.com/libretiny-eu/libretiny.git#v1.11.0
platform = libretiny@1.9.2
framework = arduino
lib_compat_mode = soft
lib_deps =

View File

@@ -1,5 +1,5 @@
[build-system]
requires = ["setuptools==80.10.2", "wheel>=0.43,<0.47"]
requires = ["setuptools==80.10.1", "wheel>=0.43,<0.47"]
build-backend = "setuptools.build_meta"
[project]

View File

@@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
esptool==5.1.0
click==8.1.7
esphome-dashboard==20260110.0
aioesphomeapi==43.14.0
aioesphomeapi==43.13.0
zeroconf==0.148.0
puremagic==1.30
ruamel.yaml==0.19.1 # dashboard_import

View File

@@ -1,7 +0,0 @@
substitutions:
verify_ssl: "true"
http_request:
ca_certificate_path: $component_dir/test_ca.pem
<<: !include common.yaml

View File

@@ -1,10 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIBkTCB+wIJAKHBfpegPjMCMA0GCSqGSIb3DQEBCwUAMBExDzANBgNVBAMMBnVu
dXNlZDAeFw0yNDAxMDEwMDAwMDBaFw0yNTAxMDEwMDAwMDBaMBExDzANBgNVBAMM
BnVudXNlZDBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQC5mMUB1hOgLmlnXtsvcGMP
XkhAqZaR0dDPW5OS8VEopWLJCX9Y0cvNCqiDI8cnP8pP8XJGU1hGLvA5PJzWnWZz
AgMBAAGjUzBRMB0GA1UdDgQWBBR5oQ9KqFeZOdBuAJrXxEP0dqzPtTAfBgNVHSME
GDAWgBR5oQ9KqFeZOdBuAJrXxEP0dqzPtTAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
SIb3DQEBCwUAA0EAKqZFf6+f8FPDbKyPCpssquojgn7fEXqr/I/yz0R5CowGdMms
H3WH3aKP4lLSHdPTBtfIoJi3gEIZjFxp3S1TWw==
-----END CERTIFICATE-----

View File

@@ -64,16 +64,3 @@ text_sensor:
- suffix -> SUFFIX
- map:
- PREFIX text SUFFIX -> mapped
- platform: template
name: "Test Lambda Filter"
id: test_lambda_filter
filters:
- lambda: |-
return {"[" + x + "]"};
- to_upper
- lambda: |-
if (x.length() > 10) {
return {x.substr(0, 10) + "..."};
}
return {x};

View File

@@ -56,36 +56,6 @@ text_sensor:
- prepend: "["
- append: "]"
- platform: template
name: "To Lower Sensor"
id: to_lower_sensor
filters:
- to_lower
- platform: template
name: "Lambda Sensor"
id: lambda_sensor
filters:
- lambda: |-
return {"[" + x + "]"};
- platform: template
name: "Lambda Raw State Sensor"
id: lambda_raw_state_sensor
filters:
- lambda: |-
return {x + " MODIFIED"};
- platform: template
name: "Lambda Skip Sensor"
id: lambda_skip_sensor
filters:
- lambda: |-
if (x == "skip") {
return {};
}
return {x + " passed"};
# Button to publish values and log raw_state vs state
button:
- platform: template
@@ -209,73 +179,3 @@ button:
format: "CHAINED: state='%s'"
args:
- id(chained_sensor).state.c_str()
- platform: template
name: "Test To Lower Button"
id: test_to_lower_button
on_press:
- text_sensor.template.publish:
id: to_lower_sensor
state: "HELLO WORLD"
- delay: 50ms
- logger.log:
format: "TO_LOWER: state='%s'"
args:
- id(to_lower_sensor).state.c_str()
- platform: template
name: "Test Lambda Button"
id: test_lambda_button
on_press:
- text_sensor.template.publish:
id: lambda_sensor
state: "test"
- delay: 50ms
- logger.log:
format: "LAMBDA: state='%s'"
args:
- id(lambda_sensor).state.c_str()
- platform: template
name: "Test Lambda Pass Button"
id: test_lambda_pass_button
on_press:
- text_sensor.template.publish:
id: lambda_skip_sensor
state: "value"
- delay: 50ms
- logger.log:
format: "LAMBDA_PASS: state='%s'"
args:
- id(lambda_skip_sensor).state.c_str()
- platform: template
name: "Test Lambda Skip Button"
id: test_lambda_skip_button
on_press:
- text_sensor.template.publish:
id: lambda_skip_sensor
state: "skip"
- delay: 50ms
# When lambda returns {}, the value should NOT be published
# so state should remain from previous publish (or empty if first)
- logger.log:
format: "LAMBDA_SKIP: state='%s'"
args:
- id(lambda_skip_sensor).state.c_str()
- platform: template
name: "Test Lambda Raw State Button"
id: test_lambda_raw_state_button
on_press:
- text_sensor.template.publish:
id: lambda_raw_state_sensor
state: "original"
- delay: 50ms
# Verify raw_state is preserved (not mutated) after lambda filter
# state should be "original MODIFIED", raw_state should be "original"
- logger.log:
format: "LAMBDA_RAW_STATE: state='%s' raw_state='%s'"
args:
- id(lambda_raw_state_sensor).state.c_str()
- id(lambda_raw_state_sensor).get_raw_state().c_str()

View File

@@ -42,11 +42,6 @@ async def test_text_sensor_raw_state(
map_off_future: asyncio.Future[str] = loop.create_future()
map_unknown_future: asyncio.Future[str] = loop.create_future()
chained_future: asyncio.Future[str] = loop.create_future()
to_lower_future: asyncio.Future[str] = loop.create_future()
lambda_future: asyncio.Future[str] = loop.create_future()
lambda_pass_future: asyncio.Future[str] = loop.create_future()
lambda_skip_future: asyncio.Future[str] = loop.create_future()
lambda_raw_state_future: asyncio.Future[tuple[str, str]] = loop.create_future()
# Patterns to match log output
# NO_FILTER: state='hello world' raw_state='hello world'
@@ -63,13 +58,6 @@ async def test_text_sensor_raw_state(
map_off_pattern = re.compile(r"MAP_OFF: state='([^']*)'")
map_unknown_pattern = re.compile(r"MAP_UNKNOWN: state='([^']*)'")
chained_pattern = re.compile(r"CHAINED: state='([^']*)'")
to_lower_pattern = re.compile(r"TO_LOWER: state='([^']*)'")
lambda_pattern = re.compile(r"LAMBDA: state='([^']*)'")
lambda_pass_pattern = re.compile(r"LAMBDA_PASS: state='([^']*)'")
lambda_skip_pattern = re.compile(r"LAMBDA_SKIP: state='([^']*)'")
lambda_raw_state_pattern = re.compile(
r"LAMBDA_RAW_STATE: state='([^']*)' raw_state='([^']*)'"
)
def check_output(line: str) -> None:
"""Check log output for expected messages."""
@@ -104,27 +92,6 @@ async def test_text_sensor_raw_state(
if not chained_future.done() and (match := chained_pattern.search(line)):
chained_future.set_result(match.group(1))
if not to_lower_future.done() and (match := to_lower_pattern.search(line)):
to_lower_future.set_result(match.group(1))
if not lambda_future.done() and (match := lambda_pattern.search(line)):
lambda_future.set_result(match.group(1))
if not lambda_pass_future.done() and (
match := lambda_pass_pattern.search(line)
):
lambda_pass_future.set_result(match.group(1))
if not lambda_skip_future.done() and (
match := lambda_skip_pattern.search(line)
):
lambda_skip_future.set_result(match.group(1))
if not lambda_raw_state_future.done() and (
match := lambda_raw_state_pattern.search(line)
):
lambda_raw_state_future.set_result((match.group(1), match.group(2)))
async with (
run_compiled(yaml_config, line_callback=check_output),
api_client_connected() as client,
@@ -305,111 +272,3 @@ async def test_text_sensor_raw_state(
pytest.fail("Timeout waiting for CHAINED log message")
assert state == "[value]", f"Chained failed: expected '[value]', got '{state}'"
# Test 10: to_lower filter
# "HELLO WORLD" -> "hello world"
to_lower_button = next(
(e for e in entities if "test_to_lower_button" in e.object_id.lower()),
None,
)
assert to_lower_button is not None, "Test To Lower Button not found"
client.button_command(to_lower_button.key)
try:
state = await asyncio.wait_for(to_lower_future, timeout=5.0)
except TimeoutError:
pytest.fail("Timeout waiting for TO_LOWER log message")
assert state == "hello world", (
f"to_lower failed: expected 'hello world', got '{state}'"
)
# Test 11: Lambda filter
# "test" -> "[test]"
lambda_button = next(
(e for e in entities if "test_lambda_button" in e.object_id.lower()),
None,
)
assert lambda_button is not None, "Test Lambda Button not found"
client.button_command(lambda_button.key)
try:
state = await asyncio.wait_for(lambda_future, timeout=5.0)
except TimeoutError:
pytest.fail("Timeout waiting for LAMBDA log message")
assert state == "[test]", f"Lambda failed: expected '[test]', got '{state}'"
# Test 12: Lambda filter - value passes through
# "value" -> "value passed"
lambda_pass_button = next(
(e for e in entities if "test_lambda_pass_button" in e.object_id.lower()),
None,
)
assert lambda_pass_button is not None, "Test Lambda Pass Button not found"
client.button_command(lambda_pass_button.key)
try:
state = await asyncio.wait_for(lambda_pass_future, timeout=5.0)
except TimeoutError:
pytest.fail("Timeout waiting for LAMBDA_PASS log message")
assert state == "value passed", (
f"Lambda pass failed: expected 'value passed', got '{state}'"
)
# Test 13: Lambda filter - skip publishing (return {})
# "skip" -> no publish, state remains "value passed" from previous test
lambda_skip_button = next(
(e for e in entities if "test_lambda_skip_button" in e.object_id.lower()),
None,
)
assert lambda_skip_button is not None, "Test Lambda Skip Button not found"
client.button_command(lambda_skip_button.key)
try:
state = await asyncio.wait_for(lambda_skip_future, timeout=5.0)
except TimeoutError:
pytest.fail("Timeout waiting for LAMBDA_SKIP log message")
# When lambda returns {}, value should NOT be published
# State remains from previous successful publish ("value passed")
assert state == "value passed", (
f"Lambda skip failed: expected 'value passed' (unchanged), got '{state}'"
)
# Test 14: Lambda filter - verify raw_state is preserved (not mutated)
# This is critical to verify the in-place mutation optimization is safe
# "original" -> state="original MODIFIED", raw_state="original"
lambda_raw_state_button = next(
(
e
for e in entities
if "test_lambda_raw_state_button" in e.object_id.lower()
),
None,
)
assert lambda_raw_state_button is not None, (
"Test Lambda Raw State Button not found"
)
client.button_command(lambda_raw_state_button.key)
try:
state, raw_state = await asyncio.wait_for(
lambda_raw_state_future, timeout=5.0
)
except TimeoutError:
pytest.fail("Timeout waiting for LAMBDA_RAW_STATE log message")
assert state == "original MODIFIED", (
f"Lambda raw_state test failed: expected state='original MODIFIED', "
f"got '{state}'"
)
assert raw_state == "original", (
f"Lambda raw_state test failed: raw_state was mutated! "
f"Expected 'original', got '{raw_state}'"
)
assert state != raw_state, (
f"Lambda filter should modify state but preserve raw_state. "
f"state='{state}', raw_state='{raw_state}'"
)

View File

@@ -192,20 +192,6 @@ def test_check_error_unexpected_response() -> None:
espota2.check_error([0x7F], [espota2.RESPONSE_OK, espota2.RESPONSE_AUTH_OK])
def test_check_error_empty_data() -> None:
"""Test check_error raises error when device closes connection without responding."""
with pytest.raises(
espota2.OTAError, match="Device closed connection without responding"
):
espota2.check_error([], [espota2.RESPONSE_OK])
# Also test with empty bytes
with pytest.raises(
espota2.OTAError, match="Device closed connection without responding"
):
espota2.check_error(b"", [espota2.RESPONSE_OK])
def test_send_check_with_various_data_types(mock_socket: Mock) -> None:
"""Test send_check handles different data types."""