mirror of
https://github.com/esphome/esphome.git
synced 2026-02-09 09:11:52 +00:00
Compare commits
5 Commits
web_server
...
idf_avoids
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f021df399e | ||
|
|
21bd0ff6aa | ||
|
|
d0017ded5b | ||
|
|
f4d7d06c41 | ||
|
|
c027d9116f |
@@ -47,7 +47,6 @@ void MQTTJSONLightComponent::send_discovery(JsonObject root, mqtt::SendDiscovery
|
||||
root[ESPHOME_F("schema")] = ESPHOME_F("json");
|
||||
auto traits = this->state_->get_traits();
|
||||
|
||||
root[MQTT_COLOR_MODE] = true;
|
||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
JsonArray color_modes = root[ESPHOME_F("supported_color_modes")].to<JsonArray>();
|
||||
if (traits.supports_color_mode(ColorMode::ON_OFF))
|
||||
@@ -68,10 +67,6 @@ void MQTTJSONLightComponent::send_discovery(JsonObject root, mqtt::SendDiscovery
|
||||
if (traits.supports_color_mode(ColorMode::RGB_COLD_WARM_WHITE))
|
||||
color_modes.add(ESPHOME_F("rgbww"));
|
||||
|
||||
// legacy API
|
||||
if (traits.supports_color_capability(ColorCapability::BRIGHTNESS))
|
||||
root[ESPHOME_F("brightness")] = true;
|
||||
|
||||
if (traits.supports_color_mode(ColorMode::COLOR_TEMPERATURE) ||
|
||||
traits.supports_color_mode(ColorMode::COLD_WARM_WHITE)) {
|
||||
root[MQTT_MIN_MIREDS] = traits.get_min_mireds();
|
||||
|
||||
@@ -10,35 +10,65 @@ from esphome.const import (
|
||||
CONF_OPTIONS,
|
||||
CONF_RESTORE_VALUE,
|
||||
CONF_SET_ACTION,
|
||||
CONF_UPDATE_INTERVAL,
|
||||
SCHEDULER_DONT_RUN,
|
||||
)
|
||||
from esphome.core import TimePeriodMilliseconds
|
||||
from esphome.cpp_generator import TemplateArguments
|
||||
|
||||
from .. import template_ns
|
||||
|
||||
TemplateSelect = template_ns.class_(
|
||||
"TemplateSelect", select.Select, cg.PollingComponent
|
||||
)
|
||||
TemplateSelectWithSetAction = template_ns.class_(
|
||||
"TemplateSelectWithSetAction", TemplateSelect
|
||||
)
|
||||
|
||||
|
||||
def validate(config):
|
||||
errors = []
|
||||
if CONF_LAMBDA in config:
|
||||
if config[CONF_OPTIMISTIC]:
|
||||
raise cv.Invalid("optimistic cannot be used with lambda")
|
||||
errors.append(
|
||||
cv.Invalid(
|
||||
"optimistic cannot be used with lambda", path=[CONF_OPTIMISTIC]
|
||||
)
|
||||
)
|
||||
if CONF_INITIAL_OPTION in config:
|
||||
raise cv.Invalid("initial_value cannot be used with lambda")
|
||||
errors.append(
|
||||
cv.Invalid(
|
||||
"initial_value cannot be used with lambda",
|
||||
path=[CONF_INITIAL_OPTION],
|
||||
)
|
||||
)
|
||||
if CONF_RESTORE_VALUE in config:
|
||||
raise cv.Invalid("restore_value cannot be used with lambda")
|
||||
errors.append(
|
||||
cv.Invalid(
|
||||
"restore_value cannot be used with lambda",
|
||||
path=[CONF_RESTORE_VALUE],
|
||||
)
|
||||
)
|
||||
elif CONF_INITIAL_OPTION in config:
|
||||
if config[CONF_INITIAL_OPTION] not in config[CONF_OPTIONS]:
|
||||
raise cv.Invalid(
|
||||
f"initial_option '{config[CONF_INITIAL_OPTION]}' is not a valid option [{', '.join(config[CONF_OPTIONS])}]"
|
||||
errors.append(
|
||||
cv.Invalid(
|
||||
f"initial_option '{config[CONF_INITIAL_OPTION]}' is not a valid option [{', '.join(config[CONF_OPTIONS])}]",
|
||||
path=[CONF_INITIAL_OPTION],
|
||||
)
|
||||
)
|
||||
else:
|
||||
config[CONF_INITIAL_OPTION] = config[CONF_OPTIONS][0]
|
||||
|
||||
if not config[CONF_OPTIMISTIC] and CONF_SET_ACTION not in config:
|
||||
raise cv.Invalid(
|
||||
"Either optimistic mode must be enabled, or set_action must be set, to handle the option being set."
|
||||
errors.append(
|
||||
cv.Invalid(
|
||||
"Either optimistic mode must be enabled, or set_action must be set, to handle the option being set."
|
||||
)
|
||||
)
|
||||
if errors:
|
||||
raise cv.MultipleInvalid(errors)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@@ -62,29 +92,34 @@ CONFIG_SCHEMA = cv.All(
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await select.register_select(var, config, options=config[CONF_OPTIONS])
|
||||
var_id = config[CONF_ID]
|
||||
if CONF_SET_ACTION in config:
|
||||
var_id.type = TemplateSelectWithSetAction
|
||||
has_lambda = CONF_LAMBDA in config
|
||||
optimistic = config.get(CONF_OPTIMISTIC, False)
|
||||
restore_value = config.get(CONF_RESTORE_VALUE, False)
|
||||
options = config[CONF_OPTIONS]
|
||||
initial_option = config.get(CONF_INITIAL_OPTION, 0)
|
||||
initial_option_index = options.index(initial_option) if not has_lambda else 0
|
||||
|
||||
var = cg.new_Pvariable(
|
||||
var_id,
|
||||
TemplateArguments(has_lambda, optimistic, restore_value, initial_option_index),
|
||||
)
|
||||
component_config = config.copy()
|
||||
if not has_lambda:
|
||||
# No point in polling if not using a lambda
|
||||
component_config[CONF_UPDATE_INTERVAL] = TimePeriodMilliseconds(
|
||||
milliseconds=SCHEDULER_DONT_RUN
|
||||
)
|
||||
await cg.register_component(var, component_config)
|
||||
await select.register_select(var, config, options=options)
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
template_ = await cg.process_lambda(
|
||||
lambda_ = await cg.process_lambda(
|
||||
config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.std_string)
|
||||
)
|
||||
cg.add(var.set_template(template_))
|
||||
|
||||
else:
|
||||
# Only set if non-default to avoid bloating setup() function
|
||||
if config[CONF_OPTIMISTIC]:
|
||||
cg.add(var.set_optimistic(True))
|
||||
initial_option_index = config[CONF_OPTIONS].index(config[CONF_INITIAL_OPTION])
|
||||
# Only set if non-zero to avoid bloating setup() function
|
||||
# (initial_option_index_ is zero-initialized in the header)
|
||||
if initial_option_index != 0:
|
||||
cg.add(var.set_initial_option_index(initial_option_index))
|
||||
|
||||
# Only set if True (default is False)
|
||||
if config.get(CONF_RESTORE_VALUE):
|
||||
cg.add(var.set_restore_value(True))
|
||||
cg.add(var.set_lambda(lambda_))
|
||||
|
||||
if CONF_SET_ACTION in config:
|
||||
await automation.build_automation(
|
||||
|
||||
@@ -5,61 +5,44 @@ namespace esphome::template_ {
|
||||
|
||||
static const char *const TAG = "template.select";
|
||||
|
||||
void TemplateSelect::setup() {
|
||||
if (this->f_.has_value())
|
||||
return;
|
||||
|
||||
size_t index = this->initial_option_index_;
|
||||
if (this->restore_value_) {
|
||||
this->pref_ = this->make_entity_preference<size_t>();
|
||||
size_t restored_index;
|
||||
if (this->pref_.load(&restored_index) && this->has_index(restored_index)) {
|
||||
index = restored_index;
|
||||
ESP_LOGD(TAG, "State from restore: %s", this->option_at(index));
|
||||
} else {
|
||||
ESP_LOGD(TAG, "State from initial (could not load or invalid stored index): %s", this->option_at(index));
|
||||
}
|
||||
void dump_config_helper(BaseTemplateSelect *sel_comp, bool optimistic, bool has_lambda,
|
||||
const size_t initial_option_index, bool restore_value) {
|
||||
LOG_SELECT("", "Template Select", sel_comp);
|
||||
if (has_lambda) {
|
||||
LOG_UPDATE_INTERVAL(sel_comp);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "State from initial: %s", this->option_at(index));
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Optimistic: %s\n"
|
||||
" Initial Option: %s\n"
|
||||
" Restore Value: %s",
|
||||
YESNO(optimistic), sel_comp->option_at(initial_option_index), YESNO(restore_value));
|
||||
}
|
||||
|
||||
this->publish_state(index);
|
||||
}
|
||||
|
||||
void TemplateSelect::update() {
|
||||
if (!this->f_.has_value())
|
||||
return;
|
||||
void setup_initial(BaseTemplateSelect *sel_comp, size_t initial_index) {
|
||||
ESP_LOGD(TAG, "State from initial: %s", sel_comp->option_at(initial_index));
|
||||
sel_comp->publish_state(initial_index);
|
||||
}
|
||||
|
||||
auto val = this->f_();
|
||||
void setup_with_restore(BaseTemplateSelect *sel_comp, ESPPreferenceObject &pref, size_t initial_index) {
|
||||
size_t index = initial_index;
|
||||
if (pref.load(&index) && sel_comp->has_index(index)) {
|
||||
ESP_LOGD(TAG, "State from restore: %s", sel_comp->option_at(index));
|
||||
} else {
|
||||
index = initial_index;
|
||||
ESP_LOGD(TAG, "State from initial (no valid stored index): %s", sel_comp->option_at(initial_index));
|
||||
}
|
||||
sel_comp->publish_state(index);
|
||||
}
|
||||
|
||||
void update_lambda(BaseTemplateSelect *sel_comp, const optional<std::string> &val) {
|
||||
if (val.has_value()) {
|
||||
if (!this->has_option(*val)) {
|
||||
if (!sel_comp->has_option(*val)) {
|
||||
ESP_LOGE(TAG, "Lambda returned an invalid option: %s", (*val).c_str());
|
||||
return;
|
||||
}
|
||||
this->publish_state(*val);
|
||||
sel_comp->publish_state(*val);
|
||||
}
|
||||
}
|
||||
|
||||
void TemplateSelect::control(size_t index) {
|
||||
this->set_trigger_->trigger(StringRef(this->option_at(index)));
|
||||
|
||||
if (this->optimistic_)
|
||||
this->publish_state(index);
|
||||
|
||||
if (this->restore_value_)
|
||||
this->pref_.save(&index);
|
||||
}
|
||||
|
||||
void TemplateSelect::dump_config() {
|
||||
LOG_SELECT("", "Template Select", this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
if (this->f_.has_value())
|
||||
return;
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Optimistic: %s\n"
|
||||
" Initial Option: %s\n"
|
||||
" Restore Value: %s",
|
||||
YESNO(this->optimistic_), this->option_at(this->initial_option_index_), YESNO(this->restore_value_));
|
||||
}
|
||||
|
||||
} // namespace esphome::template_
|
||||
|
||||
@@ -9,29 +9,71 @@
|
||||
|
||||
namespace esphome::template_ {
|
||||
|
||||
class TemplateSelect final : public select::Select, public PollingComponent {
|
||||
public:
|
||||
template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }
|
||||
struct Empty {};
|
||||
class BaseTemplateSelect : public select::Select, public PollingComponent {};
|
||||
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
void dump_config_helper(BaseTemplateSelect *sel_comp, bool optimistic, bool has_lambda, size_t initial_option_index,
|
||||
bool restore_value);
|
||||
void setup_initial(BaseTemplateSelect *sel_comp, size_t initial_index);
|
||||
void setup_with_restore(BaseTemplateSelect *sel_comp, ESPPreferenceObject &pref, size_t initial_index);
|
||||
void update_lambda(BaseTemplateSelect *sel_comp, const optional<std::string> &val);
|
||||
|
||||
/// Base template select class - used when no set_action is configured
|
||||
|
||||
template<bool HAS_LAMBDA, bool OPTIMISTIC, bool RESTORE_VALUE, size_t INITIAL_OPTION_INDEX>
|
||||
class TemplateSelect : public BaseTemplateSelect {
|
||||
public:
|
||||
template<typename F> void set_lambda(F &&f) {
|
||||
if constexpr (HAS_LAMBDA) {
|
||||
this->f_.set(std::forward<F>(f));
|
||||
}
|
||||
}
|
||||
|
||||
void setup() override {
|
||||
if constexpr (!HAS_LAMBDA) {
|
||||
if constexpr (RESTORE_VALUE) {
|
||||
this->pref_ = this->template make_entity_preference<size_t>();
|
||||
setup_with_restore(this, this->pref_, INITIAL_OPTION_INDEX);
|
||||
} else {
|
||||
setup_initial(this, INITIAL_OPTION_INDEX);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void update() override {
|
||||
if constexpr (HAS_LAMBDA) {
|
||||
update_lambda(this, this->f_());
|
||||
}
|
||||
}
|
||||
void dump_config() override {
|
||||
dump_config_helper(this, OPTIMISTIC, HAS_LAMBDA, INITIAL_OPTION_INDEX, RESTORE_VALUE);
|
||||
};
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
Trigger<StringRef> *get_set_trigger() const { return this->set_trigger_; }
|
||||
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||
void set_initial_option_index(size_t initial_option_index) { this->initial_option_index_ = initial_option_index; }
|
||||
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
|
||||
protected:
|
||||
void control(size_t index) override {
|
||||
if constexpr (OPTIMISTIC)
|
||||
this->publish_state(index);
|
||||
if constexpr (RESTORE_VALUE)
|
||||
this->pref_.save(&index);
|
||||
}
|
||||
[[no_unique_address]] std::conditional_t<HAS_LAMBDA, TemplateLambda<std::string>, Empty> f_{};
|
||||
[[no_unique_address]] std::conditional_t<RESTORE_VALUE, ESPPreferenceObject, Empty> pref_{};
|
||||
};
|
||||
|
||||
/// Template select with set_action trigger - only instantiated when set_action is configured
|
||||
template<bool HAS_LAMBDA, bool OPTIMISTIC, bool RESTORE_VALUE, size_t INITIAL_OPTION_INDEX>
|
||||
class TemplateSelectWithSetAction final
|
||||
: public TemplateSelect<HAS_LAMBDA, OPTIMISTIC, RESTORE_VALUE, INITIAL_OPTION_INDEX> {
|
||||
public:
|
||||
Trigger<StringRef> *get_set_trigger() { return &this->set_trigger_; }
|
||||
|
||||
protected:
|
||||
void control(size_t index) override;
|
||||
bool optimistic_ = false;
|
||||
size_t initial_option_index_{0};
|
||||
bool restore_value_ = false;
|
||||
Trigger<StringRef> *set_trigger_ = new Trigger<StringRef>();
|
||||
TemplateLambda<std::string> f_;
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
void control(size_t index) override {
|
||||
this->set_trigger_.trigger(StringRef(this->option_at(index)));
|
||||
TemplateSelect<HAS_LAMBDA, OPTIMISTIC, RESTORE_VALUE, INITIAL_OPTION_INDEX>::control(index);
|
||||
}
|
||||
Trigger<StringRef> set_trigger_;
|
||||
};
|
||||
|
||||
} // namespace esphome::template_
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
#include "StreamString.h"
|
||||
#endif
|
||||
|
||||
#include <chrono>
|
||||
#include <cinttypes>
|
||||
#include <cstdlib>
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
@@ -367,13 +365,7 @@ void WebServer::set_css_include(const char *css_include) { this->css_include_ =
|
||||
void WebServer::set_js_include(const char *js_include) { this->js_include_ = js_include; }
|
||||
#endif
|
||||
|
||||
/// Get uptime in milliseconds using std::chrono::steady_clock (64-bit, no rollover)
|
||||
static int64_t get_uptime_ms() {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch())
|
||||
.count();
|
||||
}
|
||||
|
||||
json::SerializationBuffer<> WebServer::get_config_json() {
|
||||
std::string WebServer::get_config_json() {
|
||||
json::JsonBuilder builder;
|
||||
JsonObject root = builder.root();
|
||||
|
||||
@@ -388,7 +380,6 @@ json::SerializationBuffer<> WebServer::get_config_json() {
|
||||
#endif
|
||||
root[ESPHOME_F("log")] = this->expose_log_;
|
||||
root[ESPHOME_F("lang")] = "en";
|
||||
root[ESPHOME_F("uptime")] = get_uptime_ms();
|
||||
|
||||
return builder.serialize();
|
||||
}
|
||||
@@ -412,11 +403,7 @@ void WebServer::setup() {
|
||||
|
||||
// doesn't need defer functionality - if the queue is full, the client JS knows it's alive because it's clearly
|
||||
// getting a lot of events
|
||||
this->set_interval(10000, [this]() {
|
||||
char buf[32];
|
||||
buf_append_printf(buf, sizeof(buf), 0, "{\"uptime\":%" PRId64 "}", get_uptime_ms());
|
||||
this->events_.try_send_nodefer(buf, "ping", millis(), 30000);
|
||||
});
|
||||
this->set_interval(10000, [this]() { this->events_.try_send_nodefer("", "ping", millis(), 30000); });
|
||||
}
|
||||
void WebServer::loop() { this->events_.loop(); }
|
||||
|
||||
|
||||
@@ -73,18 +73,15 @@ optional<std::string> request_get_url_query(httpd_req_t *req) {
|
||||
return {str};
|
||||
}
|
||||
|
||||
optional<std::string> query_key_value(const std::string &query_url, const std::string &key) {
|
||||
if (query_url.empty()) {
|
||||
optional<std::string> query_key_value(const char *query_url, size_t query_len, const char *key) {
|
||||
if (query_url == nullptr || query_len == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto val = std::unique_ptr<char[]>(new char[query_url.size()]);
|
||||
if (!val) {
|
||||
ESP_LOGE(TAG, "Not enough memory to the query key value");
|
||||
return {};
|
||||
}
|
||||
// Use stack buffer for typical query strings, heap fallback for large ones
|
||||
SmallBufferWithHeapFallback<256, char> val(query_len);
|
||||
|
||||
if (httpd_query_key_value(query_url.c_str(), key.c_str(), val.get(), query_url.size()) != ESP_OK) {
|
||||
if (httpd_query_key_value(query_url, key, val.get(), query_len) != ESP_OK) {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,10 @@ size_t url_decode(char *str);
|
||||
bool request_has_header(httpd_req_t *req, const char *name);
|
||||
optional<std::string> request_get_header(httpd_req_t *req, const char *name);
|
||||
optional<std::string> request_get_url_query(httpd_req_t *req);
|
||||
optional<std::string> query_key_value(const std::string &query_url, const std::string &key);
|
||||
optional<std::string> query_key_value(const char *query_url, size_t query_len, const char *key);
|
||||
inline optional<std::string> query_key_value(const std::string &query_url, const std::string &key) {
|
||||
return query_key_value(query_url.c_str(), query_url.size(), key.c_str());
|
||||
}
|
||||
|
||||
// Helper function for case-insensitive character comparison
|
||||
inline bool char_equals_ci(char a, char b) { return ::tolower(a) == ::tolower(b); }
|
||||
|
||||
@@ -366,7 +366,7 @@ void AsyncWebServerRequest::requestAuthentication(const char *realm) const {
|
||||
}
|
||||
#endif
|
||||
|
||||
AsyncWebParameter *AsyncWebServerRequest::getParam(const std::string &name) {
|
||||
AsyncWebParameter *AsyncWebServerRequest::getParam(const char *name) {
|
||||
// Check cache first - only successful lookups are cached
|
||||
for (auto *param : this->params_) {
|
||||
if (param->name() == name) {
|
||||
@@ -375,11 +375,11 @@ AsyncWebParameter *AsyncWebServerRequest::getParam(const std::string &name) {
|
||||
}
|
||||
|
||||
// Look up value from query strings
|
||||
optional<std::string> val = query_key_value(this->post_query_, name);
|
||||
optional<std::string> val = query_key_value(this->post_query_.c_str(), this->post_query_.size(), name);
|
||||
if (!val.has_value()) {
|
||||
auto url_query = request_get_url_query(*this);
|
||||
if (url_query.has_value()) {
|
||||
val = query_key_value(url_query.value(), name);
|
||||
val = query_key_value(url_query.value().c_str(), url_query.value().size(), name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,19 +162,24 @@ class AsyncWebServerRequest {
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
bool hasParam(const std::string &name) { return this->getParam(name) != nullptr; }
|
||||
bool hasParam(const char *name) { return this->getParam(name) != nullptr; }
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
AsyncWebParameter *getParam(const std::string &name);
|
||||
bool hasParam(const std::string &name) { return this->getParam(name.c_str()) != nullptr; }
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
AsyncWebParameter *getParam(const char *name);
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
AsyncWebParameter *getParam(const std::string &name) { return this->getParam(name.c_str()); }
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
bool hasArg(const char *name) { return this->hasParam(name); }
|
||||
std::string arg(const std::string &name) {
|
||||
std::string arg(const char *name) {
|
||||
auto *param = this->getParam(name);
|
||||
if (param) {
|
||||
return param->value();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
std::string arg(const std::string &name) { return this->arg(name.c_str()); }
|
||||
|
||||
operator httpd_req_t *() const { return this->req_; }
|
||||
optional<std::string> get_header(const char *name) const;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart_2400/esp32-ard.yaml
|
||||
uart_2400: !include ../../test_build_components/common/uart_2400/esp32-ard.yaml
|
||||
|
||||
<<: !include common-generic.yaml
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart_2400/esp32-idf.yaml
|
||||
uart_2400: !include ../../test_build_components/common/uart_2400/esp32-idf.yaml
|
||||
|
||||
<<: !include common-netznoe.yaml
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart_2400/esp8266-ard.yaml
|
||||
uart_2400: !include ../../test_build_components/common/uart_2400/esp8266-ard.yaml
|
||||
|
||||
<<: !include common-generic.yaml
|
||||
|
||||
@@ -296,6 +296,16 @@ select:
|
||||
// Migration guide: Store in std::string
|
||||
std::string stored_option(id(template_select).current_option());
|
||||
ESP_LOGI("test", "Stored: %s", stored_option.c_str());
|
||||
- platform: template
|
||||
id: template_select_with_action
|
||||
name: "Template select with action"
|
||||
options:
|
||||
- option_a
|
||||
- option_b
|
||||
set_action:
|
||||
- logger.log:
|
||||
format: "Selected: %s"
|
||||
args: ["x.c_str()"]
|
||||
|
||||
lock:
|
||||
- platform: template
|
||||
|
||||
@@ -56,7 +56,21 @@ select:
|
||||
std::string prefix = x.substr(0, 6);
|
||||
ESP_LOGI("test", "Substr prefix: %s", prefix.c_str());
|
||||
|
||||
# Second select with numeric options to test ADL functions
|
||||
# Second select with set_action trigger (uses TemplateSelectWithSetAction subclass)
|
||||
- platform: template
|
||||
name: "Action Select"
|
||||
id: action_select
|
||||
options:
|
||||
- "Action A"
|
||||
- "Action B"
|
||||
set_action:
|
||||
then:
|
||||
# Test: set_action trigger receives StringRef
|
||||
- logger.log:
|
||||
format: "set_action triggered: %s"
|
||||
args: ['x.c_str()']
|
||||
|
||||
# Third select with numeric options to test ADL functions
|
||||
- platform: template
|
||||
name: "Baud Rate"
|
||||
id: baud_select
|
||||
|
||||
@@ -28,6 +28,8 @@ async def test_select_stringref_trigger(
|
||||
find_substr_future = loop.create_future()
|
||||
find_char_future = loop.create_future()
|
||||
substr_future = loop.create_future()
|
||||
# set_action trigger (TemplateSelectWithSetAction subclass)
|
||||
set_action_future = loop.create_future()
|
||||
# ADL functions
|
||||
stoi_future = loop.create_future()
|
||||
stol_future = loop.create_future()
|
||||
@@ -43,6 +45,8 @@ async def test_select_stringref_trigger(
|
||||
find_substr_pattern = re.compile(r"Found 'Option' in value")
|
||||
find_char_pattern = re.compile(r"Space at position: 6") # space at index 6
|
||||
substr_pattern = re.compile(r"Substr prefix: Option")
|
||||
# set_action trigger pattern (TemplateSelectWithSetAction subclass)
|
||||
set_action_pattern = re.compile(r"set_action triggered: Action B")
|
||||
# ADL function patterns (115200 from baud rate select)
|
||||
stoi_pattern = re.compile(r"stoi result: 115200")
|
||||
stol_pattern = re.compile(r"stol result: 115200")
|
||||
@@ -67,6 +71,9 @@ async def test_select_stringref_trigger(
|
||||
find_char_future.set_result(True)
|
||||
if not substr_future.done() and substr_pattern.search(line):
|
||||
substr_future.set_result(True)
|
||||
# set_action trigger
|
||||
if not set_action_future.done() and set_action_pattern.search(line):
|
||||
set_action_future.set_result(True)
|
||||
# ADL functions
|
||||
if not stoi_future.done() and stoi_pattern.search(line):
|
||||
stoi_future.set_result(True)
|
||||
@@ -89,22 +96,21 @@ async def test_select_stringref_trigger(
|
||||
# List entities to find our select
|
||||
entities, _ = await client.list_entities_services()
|
||||
|
||||
select_entity = next(
|
||||
(e for e in entities if hasattr(e, "options") and e.name == "Test Select"),
|
||||
None,
|
||||
)
|
||||
select_entity = next((e for e in entities if e.name == "Test Select"), None)
|
||||
assert select_entity is not None, "Test Select entity not found"
|
||||
|
||||
baud_entity = next(
|
||||
(e for e in entities if hasattr(e, "options") and e.name == "Baud Rate"),
|
||||
None,
|
||||
)
|
||||
baud_entity = next((e for e in entities if e.name == "Baud Rate"), None)
|
||||
assert baud_entity is not None, "Baud Rate entity not found"
|
||||
|
||||
action_entity = next((e for e in entities if e.name == "Action Select"), None)
|
||||
assert action_entity is not None, "Action Select entity not found"
|
||||
|
||||
# Change select to Option B - this should trigger on_value with StringRef
|
||||
client.select_command(select_entity.key, "Option B")
|
||||
# Change baud to 115200 - this tests ADL functions (stoi, stol, stof, stod)
|
||||
client.select_command(baud_entity.key, "115200")
|
||||
# Change action select - tests set_action trigger (TemplateSelectWithSetAction)
|
||||
client.select_command(action_entity.key, "Action B")
|
||||
|
||||
# Wait for all log messages confirming StringRef operations work
|
||||
try:
|
||||
@@ -118,6 +124,7 @@ async def test_select_stringref_trigger(
|
||||
find_substr_future,
|
||||
find_char_future,
|
||||
substr_future,
|
||||
set_action_future,
|
||||
stoi_future,
|
||||
stol_future,
|
||||
stof_future,
|
||||
@@ -135,6 +142,7 @@ async def test_select_stringref_trigger(
|
||||
"find_substr": find_substr_future.done(),
|
||||
"find_char": find_char_future.done(),
|
||||
"substr": substr_future.done(),
|
||||
"set_action": set_action_future.done(),
|
||||
"stoi": stoi_future.done(),
|
||||
"stol": stol_future.done(),
|
||||
"stof": stof_future.done(),
|
||||
|
||||
Reference in New Issue
Block a user