mirror of
https://github.com/esphome/esphome.git
synced 2026-01-26 22:42:06 -07:00
Compare commits
53 Commits
water_heat
...
mqtt_enum_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b445d46888 | ||
|
|
bf92d94863 | ||
|
|
9c3817f544 | ||
|
|
ee9e3315b6 | ||
|
|
67dea1e538 | ||
|
|
003b9c6c3f | ||
|
|
2f1a345905 | ||
|
|
7ef933abec | ||
|
|
4ddd40bcfb | ||
|
|
8ae901b3f1 | ||
|
|
bc49174920 | ||
|
|
123ee02d39 | ||
|
|
0cc8055757 | ||
|
|
27a212c14d | ||
|
|
65dc182526 | ||
|
|
dd91039ff1 | ||
|
|
6da4f95258 | ||
|
|
0207e6e8b5 | ||
|
|
ee6e12913c | ||
|
|
d95ef154aa | ||
|
|
1c9a9c7536 | ||
|
|
011407ea8b | ||
|
|
1141e83a7c | ||
|
|
214ce95cf3 | ||
|
|
3a7b83ba93 | ||
|
|
cc2f3d85dc | ||
|
|
723f67d5e2 | ||
|
|
70e45706d9 | ||
|
|
56a2a2269f | ||
|
|
d6841ba33a | ||
|
|
10cbd0164a | ||
|
|
d285706b41 | ||
|
|
ef469c20df | ||
|
|
6870d3dc50 | ||
|
|
9cc39621a6 | ||
|
|
c4f7d09553 | ||
|
|
ab1661ef22 | ||
|
|
ccbf17d5ab | ||
|
|
bac96086be | ||
|
|
c32e4bc65b | ||
|
|
993765d732 | ||
|
|
e006216ad3 | ||
|
|
d66d05dbfc | ||
|
|
bba447e656 | ||
|
|
77b6720a25 | ||
|
|
2f8f052f43 | ||
|
|
86e70c7e76 | ||
|
|
40025bb277 | ||
|
|
438bb96687 | ||
|
|
c1e1325af2 | ||
|
|
944194e04e | ||
|
|
d27d6d64da | ||
|
|
2182d1e9f0 |
@@ -1 +1 @@
|
||||
15dc295268b2dcf75942f42759b3ddec64eba89f75525698eb39c95a7f4b14ce
|
||||
d565b0589e35e692b5f2fc0c14723a99595b4828a3a3ef96c442e86a23176c00
|
||||
|
||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@19b2f06db2b6f5108140aeb04014ef02b648f789 # v4.31.11
|
||||
uses: github/codeql-action/init@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
|
||||
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@19b2f06db2b6f5108140aeb04014ef02b648f789 # v4.31.11
|
||||
uses: github/codeql-action/analyze@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -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 (strcasecmp(command, "OPEN") == 0) {
|
||||
if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("OPEN")) == 0) {
|
||||
this->set_command_open();
|
||||
} else if (strcasecmp(command, "CLOSE") == 0) {
|
||||
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("CLOSE")) == 0) {
|
||||
this->set_command_close();
|
||||
} else if (strcasecmp(command, "STOP") == 0) {
|
||||
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("STOP")) == 0) {
|
||||
this->set_command_stop();
|
||||
} else if (strcasecmp(command, "TOGGLE") == 0) {
|
||||
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("TOGGLE")) == 0) {
|
||||
this->set_command_toggle();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command);
|
||||
|
||||
@@ -12,6 +12,7 @@ from esphome.const import (
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import add_define
|
||||
|
||||
CODEOWNERS = ["@swoboda1337"]
|
||||
|
||||
@@ -42,6 +43,7 @@ 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",
|
||||
|
||||
@@ -126,7 +126,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
cv.Optional(CONF_CA_CERTIFICATE_PATH): cv.All(
|
||||
cv.file_,
|
||||
cv.only_on(PLATFORM_HOST),
|
||||
cv.Any(cv.only_on(PLATFORM_HOST), cv.only_on_esp32),
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
@@ -160,7 +160,14 @@ async def to_code(config):
|
||||
cg.add(var.set_verify_ssl(config[CONF_VERIFY_SSL]))
|
||||
|
||||
if config.get(CONF_VERIFY_SSL):
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True)
|
||||
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_ESP_TLS_INSECURE",
|
||||
|
||||
@@ -27,8 +27,9 @@ void HttpRequestIDF::dump_config() {
|
||||
HttpRequestComponent::dump_config();
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Buffer Size RX: %u\n"
|
||||
" Buffer Size TX: %u",
|
||||
this->buffer_size_rx_, this->buffer_size_tx_);
|
||||
" Buffer Size TX: %u\n"
|
||||
" Custom CA Certificate: %s",
|
||||
this->buffer_size_rx_, this->buffer_size_tx_, YESNO(this->ca_certificate_ != nullptr));
|
||||
}
|
||||
|
||||
esp_err_t HttpRequestIDF::http_event_handler(esp_http_client_event_t *evt) {
|
||||
@@ -88,11 +89,15 @@ 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 CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
|
||||
if (secure && this->verify_ssl_) {
|
||||
config.crt_bundle_attach = esp_crt_bundle_attach;
|
||||
}
|
||||
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 (this->useragent_ != nullptr) {
|
||||
config.user_agent = this->useragent_;
|
||||
|
||||
@@ -35,6 +35,7 @@ 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,
|
||||
@@ -44,6 +45,7 @@ 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);
|
||||
|
||||
@@ -82,7 +82,7 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
|
||||
uint32_t last_progress = 0;
|
||||
uint32_t update_start_time = millis();
|
||||
md5::MD5Digest md5_receive;
|
||||
std::unique_ptr<char[]> md5_receive_str(new char[33]);
|
||||
char md5_receive_str[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.get());
|
||||
this->md5_computed_ = md5_receive_str.get();
|
||||
md5_receive.get_hex(md5_receive_str);
|
||||
this->md5_computed_ = md5_receive_str;
|
||||
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.get());
|
||||
backend->set_update_md5(md5_receive_str);
|
||||
}
|
||||
|
||||
container->end();
|
||||
|
||||
@@ -191,10 +191,17 @@ 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, 9, 2), "https://github.com/libretiny-eu/libretiny.git"),
|
||||
"latest": (cv.Version(1, 9, 2), "libretiny"),
|
||||
"recommended": (cv.Version(1, 9, 2), None),
|
||||
"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",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,9 +7,7 @@ namespace esphome::light {
|
||||
|
||||
class AddressableLightWrapper : public light::AddressableLight {
|
||||
public:
|
||||
explicit AddressableLightWrapper(light::LightState *light_state) : light_state_(light_state) {
|
||||
this->wrapper_state_ = new uint8_t[5]; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
explicit AddressableLightWrapper(light::LightState *light_state) : light_state_(light_state) {}
|
||||
|
||||
int32_t size() const override { return 1; }
|
||||
|
||||
@@ -118,7 +116,7 @@ class AddressableLightWrapper : public light::AddressableLight {
|
||||
}
|
||||
|
||||
light::LightState *light_state_;
|
||||
uint8_t *wrapper_state_;
|
||||
mutable uint8_t wrapper_state_[5]{};
|
||||
ColorMode color_mode_{ColorMode::UNKNOWN};
|
||||
};
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace mhz19 {
|
||||
namespace esphome::mhz19 {
|
||||
|
||||
static const char *const TAG = "mhz19";
|
||||
static const uint8_t MHZ19_REQUEST_LENGTH = 8;
|
||||
@@ -17,6 +16,19 @@ 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++) {
|
||||
@@ -91,24 +103,24 @@ void MHZ19Component::abc_disable() {
|
||||
this->mhz19_write_command_(MHZ19_COMMAND_ABC_DISABLE, nullptr);
|
||||
}
|
||||
|
||||
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;
|
||||
void MHZ19Component::range_set(MHZ19DetectionRange detection_range) {
|
||||
const uint8_t *command;
|
||||
switch (detection_range) {
|
||||
case MHZ19_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);
|
||||
command = MHZ19_COMMAND_DETECTION_RANGE_0_2000PPM;
|
||||
break;
|
||||
case MHZ19_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);
|
||||
command = MHZ19_COMMAND_DETECTION_RANGE_0_5000PPM;
|
||||
break;
|
||||
case MHZ19_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);
|
||||
command = MHZ19_COMMAND_DETECTION_RANGE_0_10000PPM;
|
||||
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) {
|
||||
@@ -140,24 +152,7 @@ void MHZ19Component::dump_config() {
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Warmup time: %" PRIu32 " s", this->warmup_seconds_);
|
||||
|
||||
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);
|
||||
ESP_LOGCONFIG(TAG, " Detection range: %s", LOG_STR_ARG(detection_range_to_log_string(this->detection_range_)));
|
||||
}
|
||||
|
||||
} // namespace mhz19
|
||||
} // namespace esphome
|
||||
} // namespace esphome::mhz19
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mhz19 {
|
||||
namespace esphome::mhz19 {
|
||||
|
||||
enum MHZ19ABCLogic {
|
||||
MHZ19_ABC_NONE = 0,
|
||||
@@ -32,7 +31,7 @@ class MHZ19Component : public PollingComponent, public uart::UARTDevice {
|
||||
void calibrate_zero();
|
||||
void abc_enable();
|
||||
void abc_disable();
|
||||
void range_set(MHZ19DetectionRange detection_ppm);
|
||||
void range_set(MHZ19DetectionRange detection_range);
|
||||
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_co2_sensor(sensor::Sensor *co2_sensor) { co2_sensor_ = co2_sensor; }
|
||||
@@ -74,5 +73,4 @@ template<typename... Ts> class MHZ19DetectionRangeSetAction : public Action<Ts..
|
||||
void play(const Ts &...x) override { this->parent_->range_set(this->detection_range_.value(x...)); }
|
||||
};
|
||||
|
||||
} // namespace mhz19
|
||||
} // namespace esphome
|
||||
} // namespace esphome::mhz19
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "mqtt_alarm_control_panel.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
@@ -12,6 +13,33 @@ 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() {
|
||||
@@ -84,42 +112,9 @@ const EntityBase *MQTTAlarmControlPanelComponent::get_entity() const { return th
|
||||
|
||||
bool MQTTAlarmControlPanelComponent::send_initial_state() { return this->publish_state(); }
|
||||
bool MQTTAlarmControlPanelComponent::publish_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);
|
||||
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()));
|
||||
}
|
||||
|
||||
} // namespace esphome::mqtt
|
||||
|
||||
@@ -52,8 +52,9 @@ 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_(), state_s);
|
||||
return this->publish(this->get_state_topic_to_(topic_buf), state_s);
|
||||
}
|
||||
|
||||
} // namespace esphome::mqtt
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "mqtt_climate.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
@@ -12,6 +13,111 @@ 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();
|
||||
@@ -260,34 +366,8 @@ 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(), mode_s))
|
||||
if (!this->publish(this->get_mode_state_topic(), climate_mode_to_mqtt_str(this->device_->mode)))
|
||||
success = false;
|
||||
int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals();
|
||||
int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals();
|
||||
@@ -327,134 +407,37 @@ bool MQTTClimateComponent::publish_state_() {
|
||||
}
|
||||
|
||||
if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
|
||||
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))
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_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))
|
||||
if (!this->publish(this->get_action_state_topic(), climate_action_to_mqtt_str(this->device_->action)))
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (traits.get_supports_fan_modes()) {
|
||||
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))
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (traits.get_supports_swing_modes()) {
|
||||
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))
|
||||
if (!this->publish(this->get_swing_mode_state_topic(), climate_swing_mode_to_mqtt_str(this->device_->swing_mode)))
|
||||
success = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#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"
|
||||
@@ -132,17 +133,45 @@ std::string MQTTComponent::get_command_topic_() const {
|
||||
}
|
||||
|
||||
bool MQTTComponent::publish(const std::string &topic, const std::string &payload) {
|
||||
return this->publish(topic, payload.data(), payload.size());
|
||||
return this->publish(topic.c_str(), payload.data(), payload.size());
|
||||
}
|
||||
|
||||
bool MQTTComponent::publish(const std::string &topic, const char *payload, size_t payload_length) {
|
||||
if (topic.empty())
|
||||
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')
|
||||
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) {
|
||||
if (topic.empty())
|
||||
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')
|
||||
return false;
|
||||
return global_mqtt_client->publish_json(topic, f, this->qos_, this->retain_);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#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"
|
||||
|
||||
@@ -157,6 +158,70 @@ 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.
|
||||
@@ -164,6 +229,20 @@ 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.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "mqtt_cover.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
@@ -12,6 +13,20 @@ 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();
|
||||
@@ -109,13 +124,10 @@ bool MQTTCoverComponent::publish_state() {
|
||||
if (!this->publish(this->get_tilt_state_topic(), pos, len))
|
||||
success = false;
|
||||
}
|
||||
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))
|
||||
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())))
|
||||
success = false;
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,8 @@ bool MQTTDateComponent::send_initial_state() {
|
||||
}
|
||||
}
|
||||
bool MQTTDateComponent::publish_state(uint16_t year, uint8_t month, uint8_t day) {
|
||||
return this->publish_json(this->get_state_topic_(), [year, month, day](JsonObject root) {
|
||||
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
|
||||
return this->publish_json(this->get_state_topic_to_(topic_buf), [year, month, day](JsonObject root) {
|
||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
root[ESPHOME_F("year")] = year;
|
||||
root[ESPHOME_F("month")] = month;
|
||||
|
||||
@@ -66,15 +66,17 @@ 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) {
|
||||
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;
|
||||
});
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace esphome::mqtt
|
||||
|
||||
@@ -44,7 +44,8 @@ void MQTTEventComponent::dump_config() {
|
||||
}
|
||||
|
||||
bool MQTTEventComponent::publish_event_(const std::string &event_type) {
|
||||
return this->publish_json(this->get_state_topic_(), [event_type](JsonObject root) {
|
||||
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
|
||||
return this->publish_json(this->get_state_topic_to_(topic_buf), [event_type](JsonObject root) {
|
||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
root[MQTT_EVENT_TYPE] = event_type;
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "mqtt_fan.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
@@ -12,6 +13,14 @@ 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_; }
|
||||
@@ -158,18 +167,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_(), state_s);
|
||||
this->publish(this->get_state_topic_to_(topic_buf), state_s);
|
||||
bool failed = false;
|
||||
if (this->state_->get_traits().supports_direction()) {
|
||||
bool success = this->publish(this->get_direction_state_topic(),
|
||||
this->state_->direction == fan::FanDirection::FORWARD ? "forward" : "reverse");
|
||||
bool success = this->publish(this->get_direction_state_topic(), fan_direction_to_mqtt_str(this->state_->direction));
|
||||
failed = failed || !success;
|
||||
}
|
||||
if (this->state_->get_traits().supports_oscillation()) {
|
||||
bool success = this->publish(this->get_oscillation_state_topic(),
|
||||
this->state_->oscillating ? "oscillate_on" : "oscillate_off");
|
||||
bool success =
|
||||
this->publish(this->get_oscillation_state_topic(), fan_oscillation_to_mqtt_str(this->state_->oscillating));
|
||||
failed = failed || !success;
|
||||
}
|
||||
auto traits = this->state_->get_traits();
|
||||
|
||||
@@ -34,7 +34,8 @@ void MQTTJSONLightComponent::on_light_remote_values_update() {
|
||||
MQTTJSONLightComponent::MQTTJSONLightComponent(LightState *state) : state_(state) {}
|
||||
|
||||
bool MQTTJSONLightComponent::publish_state_() {
|
||||
return this->publish_json(this->get_state_topic_(), [this](JsonObject root) {
|
||||
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
|
||||
return this->publish_json(this->get_state_topic_to_(topic_buf), [this](JsonObject root) {
|
||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
LightJSONSchema::dump_json(*this->state_, root);
|
||||
});
|
||||
|
||||
@@ -47,13 +47,14 @@ 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_(), buf);
|
||||
return this->publish(this->get_state_topic_to_(topic_buf), buf);
|
||||
#else
|
||||
return this->publish(this->get_state_topic_(), LOG_STR_ARG(lock_state_to_string(this->lock_->state)));
|
||||
return this->publish(this->get_state_topic_to_(topic_buf), LOG_STR_ARG(lock_state_to_string(this->lock_->state)));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -74,9 +74,10 @@ bool MQTTNumberComponent::send_initial_state() {
|
||||
}
|
||||
}
|
||||
bool MQTTNumberComponent::publish_state(float value) {
|
||||
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
|
||||
char buffer[64];
|
||||
buf_append_printf(buffer, sizeof(buffer), 0, "%f", value);
|
||||
return this->publish(this->get_state_topic_(), buffer);
|
||||
size_t len = buf_append_printf(buffer, sizeof(buffer), 0, "%f", value);
|
||||
return this->publish(this->get_state_topic_to_(topic_buf), buffer, len);
|
||||
}
|
||||
|
||||
} // namespace esphome::mqtt
|
||||
|
||||
@@ -50,7 +50,8 @@ bool MQTTSelectComponent::send_initial_state() {
|
||||
}
|
||||
}
|
||||
bool MQTTSelectComponent::publish_state(const std::string &value) {
|
||||
return this->publish(this->get_state_topic_(), value);
|
||||
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
|
||||
return this->publish(this->get_state_topic_to_(topic_buf), value.data(), value.size());
|
||||
}
|
||||
|
||||
} // namespace esphome::mqtt
|
||||
|
||||
@@ -79,12 +79,13 @@ 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_(), "None", 4);
|
||||
return this->publish(this->get_state_topic_to_(topic_buf), "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_(), buf, len);
|
||||
return this->publish(this->get_state_topic_to_(topic_buf), buf, len);
|
||||
}
|
||||
|
||||
} // namespace esphome::mqtt
|
||||
|
||||
@@ -52,8 +52,9 @@ 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_(), state_s);
|
||||
return this->publish(this->get_state_topic_to_(topic_buf), state_s);
|
||||
}
|
||||
|
||||
} // namespace esphome::mqtt
|
||||
|
||||
@@ -53,7 +53,8 @@ bool MQTTTextComponent::send_initial_state() {
|
||||
}
|
||||
}
|
||||
bool MQTTTextComponent::publish_state(const std::string &value) {
|
||||
return this->publish(this->get_state_topic_(), value);
|
||||
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
|
||||
return this->publish(this->get_state_topic_to_(topic_buf), value.data(), value.size());
|
||||
}
|
||||
|
||||
} // namespace esphome::mqtt
|
||||
|
||||
@@ -31,7 +31,10 @@ void MQTTTextSensor::dump_config() {
|
||||
LOG_MQTT_COMPONENT(true, false);
|
||||
}
|
||||
|
||||
bool MQTTTextSensor::publish_state(const std::string &value) { return this->publish(this->get_state_topic_(), value); }
|
||||
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::send_initial_state() {
|
||||
if (this->sensor_->has_state()) {
|
||||
return this->publish_state(this->sensor_->state);
|
||||
|
||||
@@ -53,7 +53,8 @@ bool MQTTTimeComponent::send_initial_state() {
|
||||
}
|
||||
}
|
||||
bool MQTTTimeComponent::publish_state(uint8_t hour, uint8_t minute, uint8_t second) {
|
||||
return this->publish_json(this->get_state_topic_(), [hour, minute, second](JsonObject root) {
|
||||
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
|
||||
return this->publish_json(this->get_state_topic_to_(topic_buf), [hour, minute, second](JsonObject root) {
|
||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
root[ESPHOME_F("hour")] = hour;
|
||||
root[ESPHOME_F("minute")] = minute;
|
||||
|
||||
@@ -28,7 +28,8 @@ void MQTTUpdateComponent::setup() {
|
||||
}
|
||||
|
||||
bool MQTTUpdateComponent::publish_state() {
|
||||
return this->publish_json(this->get_state_topic_(), [this](JsonObject root) {
|
||||
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
|
||||
return this->publish_json(this->get_state_topic_to_(topic_buf), [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;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "mqtt_valve.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
@@ -12,6 +13,20 @@ 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();
|
||||
@@ -78,13 +93,10 @@ bool MQTTValveComponent::publish_state() {
|
||||
if (!this->publish(this->get_position_state_topic(), pos, len))
|
||||
success = false;
|
||||
}
|
||||
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))
|
||||
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())))
|
||||
success = false;
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace esphome {
|
||||
@@ -21,7 +22,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
|
||||
|
||||
const std::vector<uint8_t> START_SEQ = {0x1b, 0x1b, 0x1b, 0x1b, 0x01, 0x01, 0x01, 0x01};
|
||||
constexpr std::array<uint8_t, 8> START_SEQ = {0x1b, 0x1b, 0x1b, 0x1b, 0x01, 0x01, 0x01, 0x01};
|
||||
|
||||
} // namespace sml
|
||||
} // namespace esphome
|
||||
|
||||
@@ -20,7 +20,7 @@ from .. import template_ns
|
||||
CONF_CURRENT_TEMPERATURE = "current_temperature"
|
||||
|
||||
TemplateWaterHeater = template_ns.class_(
|
||||
"TemplateWaterHeater", water_heater.WaterHeater
|
||||
"TemplateWaterHeater", cg.Component, water_heater.WaterHeater
|
||||
)
|
||||
|
||||
TemplateWaterHeaterPublishAction = template_ns.class_(
|
||||
@@ -36,24 +36,29 @@ RESTORE_MODES = {
|
||||
"RESTORE_AND_CALL": TemplateWaterHeaterRestoreMode.WATER_HEATER_RESTORE_AND_CALL,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = water_heater.water_heater_schema(TemplateWaterHeater).extend(
|
||||
{
|
||||
cv.Optional(CONF_OPTIMISTIC, default=True): cv.boolean,
|
||||
cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_RESTORE_MODE, default="NO_RESTORE"): cv.enum(
|
||||
RESTORE_MODES, upper=True
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_TEMPERATURE): cv.returning_lambda,
|
||||
cv.Optional(CONF_MODE): cv.returning_lambda,
|
||||
cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(
|
||||
water_heater.validate_water_heater_mode
|
||||
),
|
||||
}
|
||||
CONFIG_SCHEMA = (
|
||||
water_heater.water_heater_schema(TemplateWaterHeater)
|
||||
.extend(
|
||||
{
|
||||
cv.Optional(CONF_OPTIMISTIC, default=True): cv.boolean,
|
||||
cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_RESTORE_MODE, default="NO_RESTORE"): cv.enum(
|
||||
RESTORE_MODES, upper=True
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_TEMPERATURE): cv.returning_lambda,
|
||||
cv.Optional(CONF_MODE): cv.returning_lambda,
|
||||
cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(
|
||||
water_heater.validate_water_heater_mode
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await water_heater.register_water_heater(var, config)
|
||||
|
||||
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
||||
|
||||
@@ -10,7 +10,7 @@ TemplateWaterHeater::TemplateWaterHeater() : set_trigger_(new Trigger<>()) {}
|
||||
void TemplateWaterHeater::setup() {
|
||||
if (this->restore_mode_ == TemplateWaterHeaterRestoreMode::WATER_HEATER_RESTORE ||
|
||||
this->restore_mode_ == TemplateWaterHeaterRestoreMode::WATER_HEATER_RESTORE_AND_CALL) {
|
||||
auto restore = this->restore_state();
|
||||
auto restore = this->restore_state_();
|
||||
|
||||
if (restore.has_value()) {
|
||||
restore->perform();
|
||||
|
||||
@@ -13,7 +13,7 @@ enum TemplateWaterHeaterRestoreMode {
|
||||
WATER_HEATER_RESTORE_AND_CALL,
|
||||
};
|
||||
|
||||
class TemplateWaterHeater : public water_heater::WaterHeater {
|
||||
class TemplateWaterHeater : public Component, public water_heater::WaterHeater {
|
||||
public:
|
||||
TemplateWaterHeater();
|
||||
|
||||
|
||||
@@ -35,9 +35,6 @@ 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() {
|
||||
|
||||
@@ -70,7 +70,7 @@ class TM1638Component : public PollingComponent {
|
||||
GPIOPin *clk_pin_;
|
||||
GPIOPin *stb_pin_;
|
||||
GPIOPin *dio_pin_;
|
||||
uint8_t *buffer_ = new uint8_t[8];
|
||||
uint8_t buffer_[8]{};
|
||||
tm1638_writer_t writer_{};
|
||||
std::vector<KeyListener *> listeners_{};
|
||||
};
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
#include "uln2003.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace uln2003 {
|
||||
namespace esphome::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();
|
||||
@@ -42,22 +54,7 @@ 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_);
|
||||
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);
|
||||
ESP_LOGCONFIG(TAG, " Step Mode: %s", LOG_STR_ARG(step_mode_to_log_string(this->step_mode_)));
|
||||
}
|
||||
void ULN2003::write_step_(int32_t step) {
|
||||
int32_t n = this->step_mode_ == ULN2003_STEP_MODE_HALF_STEP ? 8 : 4;
|
||||
@@ -90,5 +87,4 @@ void ULN2003::write_step_(int32_t step) {
|
||||
this->pin_d_->digital_write((res >> 3) & 1);
|
||||
}
|
||||
|
||||
} // namespace uln2003
|
||||
} // namespace esphome
|
||||
} // namespace esphome::uln2003
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/stepper/stepper.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace uln2003 {
|
||||
namespace esphome::uln2003 {
|
||||
|
||||
enum ULN2003StepMode {
|
||||
ULN2003_STEP_MODE_FULL_STEP,
|
||||
@@ -40,5 +39,4 @@ class ULN2003 : public stepper::Stepper, public Component {
|
||||
int32_t current_uln_pos_{0};
|
||||
};
|
||||
|
||||
} // namespace uln2003
|
||||
} // namespace esphome
|
||||
} // namespace esphome::uln2003
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#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 {
|
||||
@@ -38,13 +40,13 @@ Valve::Valve() : position{VALVE_OPEN} {}
|
||||
|
||||
ValveCall::ValveCall(Valve *parent) : parent_(parent) {}
|
||||
ValveCall &ValveCall::set_command(const char *command) {
|
||||
if (strcasecmp(command, "OPEN") == 0) {
|
||||
if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("OPEN")) == 0) {
|
||||
this->set_command_open();
|
||||
} else if (strcasecmp(command, "CLOSE") == 0) {
|
||||
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("CLOSE")) == 0) {
|
||||
this->set_command_close();
|
||||
} else if (strcasecmp(command, "STOP") == 0) {
|
||||
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("STOP")) == 0) {
|
||||
this->set_command_stop();
|
||||
} else if (strcasecmp(command, "TOGGLE") == 0) {
|
||||
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("TOGGLE")) == 0) {
|
||||
this->set_command_toggle();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command);
|
||||
|
||||
@@ -18,7 +18,7 @@ CODEOWNERS = ["@dhoeben"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
water_heater_ns = cg.esphome_ns.namespace("water_heater")
|
||||
WaterHeater = water_heater_ns.class_("WaterHeater", cg.EntityBase, cg.Component)
|
||||
WaterHeater = water_heater_ns.class_("WaterHeater", cg.EntityBase)
|
||||
WaterHeaterCall = water_heater_ns.class_("WaterHeaterCall")
|
||||
WaterHeaterTraits = water_heater_ns.class_("WaterHeaterTraits")
|
||||
|
||||
@@ -46,7 +46,7 @@ _WATER_HEATER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
|
||||
}
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
_WATER_HEATER_SCHEMA.add_extra(entity_duplicate_validator("water_heater"))
|
||||
|
||||
@@ -91,8 +91,6 @@ async def register_water_heater(var: cg.Pvariable, config: ConfigType) -> cg.Pva
|
||||
|
||||
cg.add_define("USE_WATER_HEATER")
|
||||
|
||||
await cg.register_component(var, config)
|
||||
|
||||
cg.add(cg.App.register_water_heater(var))
|
||||
|
||||
CORE.register_platform_component("water_heater", var)
|
||||
|
||||
@@ -146,10 +146,6 @@ void WaterHeaterCall::validate_() {
|
||||
}
|
||||
}
|
||||
|
||||
void WaterHeater::setup() {
|
||||
this->pref_ = global_preferences->make_preference<SavedWaterHeaterState>(this->get_preference_hash());
|
||||
}
|
||||
|
||||
void WaterHeater::publish_state() {
|
||||
auto traits = this->get_traits();
|
||||
ESP_LOGD(TAG,
|
||||
@@ -188,7 +184,8 @@ void WaterHeater::publish_state() {
|
||||
this->pref_.save(&saved);
|
||||
}
|
||||
|
||||
optional<WaterHeaterCall> WaterHeater::restore_state() {
|
||||
optional<WaterHeaterCall> WaterHeater::restore_state_() {
|
||||
this->pref_ = global_preferences->make_preference<SavedWaterHeaterState>(this->get_preference_hash());
|
||||
SavedWaterHeaterState recovered{};
|
||||
if (!this->pref_.load(&recovered))
|
||||
return {};
|
||||
|
||||
@@ -177,7 +177,7 @@ class WaterHeaterTraits {
|
||||
WaterHeaterModeMask supported_modes_;
|
||||
};
|
||||
|
||||
class WaterHeater : public EntityBase, public Component {
|
||||
class WaterHeater : public EntityBase {
|
||||
public:
|
||||
WaterHeaterMode get_mode() const { return this->mode_; }
|
||||
float get_current_temperature() const { return this->current_temperature_; }
|
||||
@@ -204,16 +204,15 @@ class WaterHeater : public EntityBase, public Component {
|
||||
#endif
|
||||
virtual void control(const WaterHeaterCall &call) = 0;
|
||||
|
||||
void setup() override;
|
||||
|
||||
optional<WaterHeaterCall> restore_state();
|
||||
|
||||
protected:
|
||||
virtual WaterHeaterTraits traits() = 0;
|
||||
|
||||
/// Log the traits of this water heater for dump_config().
|
||||
void dump_traits_(const char *tag);
|
||||
|
||||
/// Restore the state of the water heater, call this from your setup() method.
|
||||
optional<WaterHeaterCall> restore_state_();
|
||||
|
||||
/// Set the mode of the water heater. Should only be called from control().
|
||||
void set_mode_(WaterHeaterMode mode) { this->mode_ = mode; }
|
||||
/// Set the target temperature of the water heater. Should only be called from control().
|
||||
|
||||
@@ -698,6 +698,10 @@ 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;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#ifdef USE_WIFI_WPA2_EAP
|
||||
#if (ESP_IDF_VERSION_MAJOR >= 5) && (ESP_IDF_VERSION_MINOR >= 1)
|
||||
@@ -828,16 +829,29 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
|
||||
uint16_t number = it.number;
|
||||
scan_result_.init(number);
|
||||
|
||||
// Process one record at a time to avoid large buffer allocation
|
||||
wifi_ap_record_t record;
|
||||
#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
|
||||
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));
|
||||
|
||||
@@ -649,6 +649,10 @@ 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) {
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
#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
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#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>
|
||||
@@ -451,15 +452,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 && strcasecmp(str, "on") == 0)
|
||||
if (on == nullptr && ESPHOME_strcasecmp_P(str, ESPHOME_PSTR("on")) == 0)
|
||||
return PARSE_ON;
|
||||
if (on != nullptr && strcasecmp(str, on) == 0)
|
||||
return PARSE_ON;
|
||||
if (off == nullptr && strcasecmp(str, "off") == 0)
|
||||
if (off == nullptr && ESPHOME_strcasecmp_P(str, ESPHOME_PSTR("off")) == 0)
|
||||
return PARSE_OFF;
|
||||
if (off != nullptr && strcasecmp(str, off) == 0)
|
||||
return PARSE_OFF;
|
||||
if (strcasecmp(str, "toggle") == 0)
|
||||
if (ESPHOME_strcasecmp_P(str, ESPHOME_PSTR("toggle")) == 0)
|
||||
return PARSE_TOGGLE;
|
||||
|
||||
return PARSE_NONE;
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
#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
|
||||
@@ -21,6 +25,10 @@ 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
|
||||
|
||||
@@ -154,6 +154,12 @@ 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")
|
||||
|
||||
@@ -212,7 +212,7 @@ build_unflags =
|
||||
; This are common settings for the LibreTiny (all variants) using Arduino.
|
||||
[common:libretiny-arduino]
|
||||
extends = common:arduino
|
||||
platform = libretiny@1.9.2
|
||||
platform = https://github.com/libretiny-eu/libretiny.git#v1.11.0
|
||||
framework = arduino
|
||||
lib_compat_mode = soft
|
||||
lib_deps =
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[build-system]
|
||||
requires = ["setuptools==80.10.1", "wheel>=0.43,<0.47"]
|
||||
requires = ["setuptools==80.10.2", "wheel>=0.43,<0.47"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
|
||||
@@ -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.13.0
|
||||
aioesphomeapi==43.14.0
|
||||
zeroconf==0.148.0
|
||||
puremagic==1.30
|
||||
ruamel.yaml==0.19.1 # dashboard_import
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
substitutions:
|
||||
verify_ssl: "true"
|
||||
|
||||
http_request:
|
||||
ca_certificate_path: $component_dir/test_ca.pem
|
||||
|
||||
<<: !include common.yaml
|
||||
10
tests/components/http_request/test_ca.pem
Normal file
10
tests/components/http_request/test_ca.pem
Normal file
@@ -0,0 +1,10 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBkTCB+wIJAKHBfpegPjMCMA0GCSqGSIb3DQEBCwUAMBExDzANBgNVBAMMBnVu
|
||||
dXNlZDAeFw0yNDAxMDEwMDAwMDBaFw0yNTAxMDEwMDAwMDBaMBExDzANBgNVBAMM
|
||||
BnVudXNlZDBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQC5mMUB1hOgLmlnXtsvcGMP
|
||||
XkhAqZaR0dDPW5OS8VEopWLJCX9Y0cvNCqiDI8cnP8pP8XJGU1hGLvA5PJzWnWZz
|
||||
AgMBAAGjUzBRMB0GA1UdDgQWBBR5oQ9KqFeZOdBuAJrXxEP0dqzPtTAfBgNVHSME
|
||||
GDAWgBR5oQ9KqFeZOdBuAJrXxEP0dqzPtTAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
|
||||
SIb3DQEBCwUAA0EAKqZFf6+f8FPDbKyPCpssquojgn7fEXqr/I/yz0R5CowGdMms
|
||||
H3WH3aKP4lLSHdPTBtfIoJi3gEIZjFxp3S1TWw==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -64,3 +64,16 @@ 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};
|
||||
|
||||
@@ -56,6 +56,36 @@ 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
|
||||
@@ -179,3 +209,73 @@ 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()
|
||||
|
||||
@@ -42,6 +42,11 @@ 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'
|
||||
@@ -58,6 +63,13 @@ 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."""
|
||||
@@ -92,6 +104,27 @@ 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,
|
||||
@@ -272,3 +305,111 @@ 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}'"
|
||||
)
|
||||
|
||||
@@ -192,6 +192,20 @@ 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."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user