mirror of
https://github.com/esphome/esphome.git
synced 2025-10-31 23:21:54 +00:00
[template] Store initial option as index in template select (#11523)
This commit is contained in:
@@ -73,11 +73,18 @@ async def to_code(config):
|
||||
cg.add(var.set_template(template_))
|
||||
|
||||
else:
|
||||
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
||||
cg.add(var.set_initial_option(config[CONF_INITIAL_OPTION]))
|
||||
# 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))
|
||||
|
||||
if CONF_RESTORE_VALUE in config:
|
||||
cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE]))
|
||||
# Only set if True (default is False)
|
||||
if config.get(CONF_RESTORE_VALUE):
|
||||
cg.add(var.set_restore_value(True))
|
||||
|
||||
if CONF_SET_ACTION in config:
|
||||
await automation.build_automation(
|
||||
|
||||
@@ -10,26 +10,21 @@ void TemplateSelect::setup() {
|
||||
if (this->f_.has_value())
|
||||
return;
|
||||
|
||||
std::string value;
|
||||
if (!this->restore_value_) {
|
||||
value = this->initial_option_;
|
||||
ESP_LOGD(TAG, "State from initial: %s", value.c_str());
|
||||
} else {
|
||||
size_t index;
|
||||
size_t index = this->initial_option_index_;
|
||||
if (this->restore_value_) {
|
||||
this->pref_ = global_preferences->make_preference<size_t>(this->get_preference_hash());
|
||||
if (!this->pref_.load(&index)) {
|
||||
value = this->initial_option_;
|
||||
ESP_LOGD(TAG, "State from initial (could not load stored index): %s", value.c_str());
|
||||
} else if (!this->has_index(index)) {
|
||||
value = this->initial_option_;
|
||||
ESP_LOGD(TAG, "State from initial (restored index %d out of bounds): %s", index, value.c_str());
|
||||
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->at(index).value().c_str());
|
||||
} else {
|
||||
value = this->at(index).value();
|
||||
ESP_LOGD(TAG, "State from restore: %s", value.c_str());
|
||||
ESP_LOGD(TAG, "State from initial (could not load or invalid stored index): %s", this->at(index).value().c_str());
|
||||
}
|
||||
} else {
|
||||
ESP_LOGD(TAG, "State from initial: %s", this->at(index).value().c_str());
|
||||
}
|
||||
|
||||
this->publish_state(value);
|
||||
this->publish_state(this->at(index).value());
|
||||
}
|
||||
|
||||
void TemplateSelect::update() {
|
||||
@@ -69,7 +64,8 @@ void TemplateSelect::dump_config() {
|
||||
" Optimistic: %s\n"
|
||||
" Initial Option: %s\n"
|
||||
" Restore Value: %s",
|
||||
YESNO(this->optimistic_), this->initial_option_.c_str(), YESNO(this->restore_value_));
|
||||
YESNO(this->optimistic_), this->at(this->initial_option_index_).value().c_str(),
|
||||
YESNO(this->restore_value_));
|
||||
}
|
||||
|
||||
} // namespace template_
|
||||
|
||||
@@ -19,13 +19,13 @@ class TemplateSelect : public select::Select, public PollingComponent {
|
||||
|
||||
Trigger<std::string> *get_set_trigger() const { return this->set_trigger_; }
|
||||
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||
void set_initial_option(const std::string &initial_option) { this->initial_option_ = initial_option; }
|
||||
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(const std::string &value) override;
|
||||
bool optimistic_ = false;
|
||||
std::string initial_option_;
|
||||
size_t initial_option_index_{0};
|
||||
bool restore_value_ = false;
|
||||
Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
|
||||
optional<optional<std::string> (*)()> f_;
|
||||
|
||||
@@ -41,6 +41,17 @@ select:
|
||||
- "" # Empty string at the end
|
||||
initial_option: "Choice X"
|
||||
|
||||
- platform: template
|
||||
name: "Select Initial Option Test"
|
||||
id: select_initial_option_test
|
||||
optimistic: true
|
||||
options:
|
||||
- "First"
|
||||
- "Second"
|
||||
- "Third"
|
||||
- "Fourth"
|
||||
initial_option: "Third" # Test non-default initial option
|
||||
|
||||
# Add a sensor to ensure we have other entities in the list
|
||||
sensor:
|
||||
- platform: template
|
||||
|
||||
@@ -36,8 +36,8 @@ async def test_host_mode_empty_string_options(
|
||||
|
||||
# Find our select entities
|
||||
select_entities = [e for e in entity_info if isinstance(e, SelectInfo)]
|
||||
assert len(select_entities) == 3, (
|
||||
f"Expected 3 select entities, got {len(select_entities)}"
|
||||
assert len(select_entities) == 4, (
|
||||
f"Expected 4 select entities, got {len(select_entities)}"
|
||||
)
|
||||
|
||||
# Verify each select entity by name and check their options
|
||||
@@ -71,6 +71,15 @@ async def test_host_mode_empty_string_options(
|
||||
assert empty_last.options[2] == "Choice Z"
|
||||
assert empty_last.options[3] == "" # Empty string at end
|
||||
|
||||
# Check "Select Initial Option Test" - verify non-default initial option
|
||||
assert "Select Initial Option Test" in selects_by_name
|
||||
initial_option_test = selects_by_name["Select Initial Option Test"]
|
||||
assert len(initial_option_test.options) == 4
|
||||
assert initial_option_test.options[0] == "First"
|
||||
assert initial_option_test.options[1] == "Second"
|
||||
assert initial_option_test.options[2] == "Third"
|
||||
assert initial_option_test.options[3] == "Fourth"
|
||||
|
||||
# If we got here without protobuf decoding errors, the fix is working
|
||||
# The bug would have caused "Invalid protobuf message" errors with trailing bytes
|
||||
|
||||
@@ -78,7 +87,12 @@ async def test_host_mode_empty_string_options(
|
||||
# This ensures empty strings work properly in state messages too
|
||||
states: dict[int, EntityState] = {}
|
||||
states_received_future: asyncio.Future[None] = loop.create_future()
|
||||
expected_select_keys = {empty_first.key, empty_middle.key, empty_last.key}
|
||||
expected_select_keys = {
|
||||
empty_first.key,
|
||||
empty_middle.key,
|
||||
empty_last.key,
|
||||
initial_option_test.key,
|
||||
}
|
||||
received_select_keys = set()
|
||||
|
||||
def on_state(state: EntityState) -> None:
|
||||
@@ -109,6 +123,14 @@ async def test_host_mode_empty_string_options(
|
||||
assert empty_first.key in states
|
||||
assert empty_middle.key in states
|
||||
assert empty_last.key in states
|
||||
assert initial_option_test.key in states
|
||||
|
||||
# Verify the initial option is set correctly to "Third" (not the default "First")
|
||||
initial_state = states[initial_option_test.key]
|
||||
assert initial_state.state == "Third", (
|
||||
f"Expected initial state 'Third' but got '{initial_state.state}' - "
|
||||
f"initial_option not correctly applied"
|
||||
)
|
||||
|
||||
# The main test is that we got here without protobuf errors
|
||||
# The select entities with empty string options were properly encoded
|
||||
|
||||
Reference in New Issue
Block a user