Skip to content

Commit 46e20d8

Browse files
bdracojesserockz
andauthored
[blog] Add breaking change blog post for Select entity class optimizations (#67)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
1 parent 616cf54 commit 46e20d8

File tree

1 file changed

+262
-0
lines changed

1 file changed

+262
-0
lines changed
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
---
2+
date: 2025-11-07
3+
authors:
4+
- bdraco
5+
comments: true
6+
---
7+
8+
# Select Entity Class: Index-Based Operations and Flash Storage
9+
10+
ESPHome 2025.11.0 introduces significant optimizations to the `Select` entity class that reduce memory usage and improve performance. These changes affect external components that implement custom select entities.
11+
12+
<!-- more -->
13+
14+
## Background
15+
16+
Two related PRs optimize the Select entity class:
17+
18+
**[PR #11623](https://github.com/esphome/esphome/pull/11623): Index-Based Operations**
19+
Refactors Select to use indices internally instead of strings, eliminating redundant string storage and operations. The public `state` member is deprecated and will be removed in ESPHome 2026.5.0 (6-month migration window). This saves ~32 bytes per SelectCall operation immediately, and will save at least 28 bytes per Select instance after the deprecated `.state` member is removed (28 bytes std::string overhead + string length).
20+
21+
**[PR #11514](https://github.com/esphome/esphome/pull/11514): Store Options in Flash**
22+
Changes option storage from heap-allocated `std::vector<std::string>` to flash-stored `FixedVector<const char*>`. Real device measurements show 164-7428 bytes saved, scaling with the total number of options across all select entities. More selects or more options per select means greater savings.
23+
24+
## What's Changing
25+
26+
### For ESPHome 2025.11.0 and Later
27+
28+
**Storage Changes (Breaking - [PR #11514](https://github.com/esphome/esphome/pull/11514)):**
29+
```cpp
30+
// OLD - heap-allocated strings
31+
std::vector<std::string> options;
32+
traits.set_options(options);
33+
34+
// NEW - flash-stored string literals
35+
traits.set_options({"Option 1", "Option 2", "Option 3"});
36+
```
37+
38+
**State Access Changes (Deprecation - [PR #11623](https://github.com/esphome/esphome/pull/11623)):**
39+
```cpp
40+
// OLD - deprecated, shows warnings (works until 2026.5.0)
41+
std::string current = my_select->state;
42+
43+
// NEW - required after 2026.5.0
44+
const char *current = my_select->current_option();
45+
```
46+
47+
## Who This Affects
48+
49+
This affects **external components** that:
50+
51+
- Manually call `set_options()` on SelectTraits in C++ code (Python code generation already uses the correct syntax)
52+
- Access the `.state` member of Select objects
53+
- Iterate over or compare select options
54+
55+
**Standard YAML configurations are not affected** - Python code generation already produces initializer lists, so no YAML changes are needed. This only impacts external components that create select entities entirely in C++.
56+
57+
## Migration Guide
58+
59+
### 1. Setting Options (Required Now)
60+
61+
**In setup() methods:**
62+
```cpp
63+
// OLD
64+
std::vector<std::string> options = {"Low", "Medium", "High"};
65+
this->traits.set_options(options);
66+
67+
// NEW - use initializer list with string literals
68+
this->traits.set_options({"Low", "Medium", "High"});
69+
```
70+
71+
**For runtime-determined options** (rare), you must store the strings persistently:
72+
```cpp
73+
#include "esphome/core/helpers.h" // For FixedVector
74+
75+
class MySelect : public select::Select {
76+
protected:
77+
// Storage for actual string data (must persist for lifetime)
78+
std::vector<std::string> stored_options_;
79+
// Pointers into stored_options_
80+
FixedVector<const char*> option_ptrs_;
81+
82+
void setup() override {
83+
// Read dynamic options from device/config (truly runtime-determined)
84+
uint8_t mode_count = this->read_mode_count_from_device();
85+
this->stored_options_.resize(mode_count);
86+
for (uint8_t i = 0; i < mode_count; i++) {
87+
this->stored_options_[i] = this->read_mode_name_from_device(i);
88+
}
89+
90+
// Build pointer array pointing into stored_options_
91+
this->option_ptrs_.init(this->stored_options_.size());
92+
for (const auto &opt : this->stored_options_) {
93+
this->option_ptrs_.push_back(opt.c_str());
94+
}
95+
96+
// Set the traits (pointers remain valid because stored_options_ persists)
97+
this->traits.set_options(this->option_ptrs_);
98+
}
99+
};
100+
```
101+
102+
### 2. Accessing Options (Required Now)
103+
104+
**Reading the options list:**
105+
```cpp
106+
// OLD - copying (deleted copy constructor)
107+
auto options = traits.get_options();
108+
109+
// NEW - use const reference
110+
const auto &options = traits.get_options();
111+
112+
// Individual options are now const char*
113+
const char *option = options[0]; // Not std::string
114+
115+
// If you need std::string:
116+
std::string str = std::string(options[0]);
117+
```
118+
119+
### 3. Reading Current Selection (Deprecated, Remove by 2026.5.0)
120+
121+
**In YAML lambdas:**
122+
```yaml
123+
# OLD - shows deprecation warning (works until 2026.5.0)
124+
lambda: 'return id(my_select).state == "option1";'
125+
126+
# NEW - required after 2026.5.0, use strcmp()
127+
lambda: 'return strcmp(id(my_select).current_option(), "option1") == 0;'
128+
129+
# Or convert to std::string if you prefer == operator (less efficient)
130+
lambda: 'return std::string(id(my_select).current_option()) == "option1";'
131+
```
132+
133+
**In C++ code:**
134+
```cpp
135+
// OLD - deprecated (works until 2026.5.0)
136+
std::string current = my_select->state;
137+
ESP_LOGD(TAG, "Current: %s", my_select->state.c_str());
138+
139+
// NEW - required after 2026.5.0
140+
const char *current = my_select->current_option();
141+
ESP_LOGD(TAG, "Current: %s", current);
142+
143+
// If you need std::string:
144+
std::string current = my_select->current_option(); // Implicit conversion
145+
```
146+
147+
### 4. Publishing State (New Methods Available)
148+
149+
**Prefer index-based operations:**
150+
```cpp
151+
// OLD - string-based (still works but less efficient)
152+
this->publish_state("option1");
153+
154+
// NEW - index-based (more efficient)
155+
this->publish_state(0); // Publish by index
156+
```
157+
158+
### 5. String Comparisons
159+
160+
**When comparing options:**
161+
```cpp
162+
// OLD - std::string comparison
163+
if (options[i] == "value") { }
164+
165+
// NEW - use strcmp()
166+
if (strcmp(options[i], "value") == 0) { }
167+
168+
// BETTER - use Select helper methods
169+
auto idx = this->index_of(value);
170+
if (idx.has_value()) {
171+
this->publish_state(idx.value());
172+
}
173+
```
174+
175+
### 6. Overriding control() Method (Required)
176+
177+
**IMPORTANT:** You **must** override at least one `control()` method. If you override neither, they will call each other infinitely.
178+
179+
```cpp
180+
class MySelect : public select::Select {
181+
protected:
182+
// Option 1: String-based control (still works, but less efficient)
183+
void control(const std::string &value) override {
184+
// This version receives the string value
185+
auto idx = this->index_of(value); // strcmp lookup needed
186+
if (idx.has_value()) {
187+
this->send_to_device(idx.value());
188+
}
189+
}
190+
191+
// Option 2: Index-based control (preferred, more efficient)
192+
void control(size_t index) override {
193+
// This version receives the index directly
194+
this->send_to_device(index); // No lookup needed
195+
}
196+
};
197+
```
198+
199+
**Which to override?**
200+
- Override `control(size_t index)` (preferred) - avoids string conversions and lookups
201+
- Override `control(const std::string &value)` - if you need the actual string value
202+
- Override both (rare) - if you need different handling for each case
203+
204+
## Supporting Multiple ESPHome Versions
205+
206+
### .state Member Access (Deprecated but Still Exists)
207+
208+
The `.state` member still exists until 2026.5.0, so you can use version guards:
209+
210+
```cpp
211+
#if ESPHOME_VERSION_CODE >= VERSION_CODE(2025, 11, 0)
212+
const char *current = my_select->current_option();
213+
#else
214+
const char *current = my_select->state.c_str();
215+
#endif
216+
```
217+
218+
### Options Storage (Hard Breaking Change)
219+
220+
The old `set_options(std::vector<std::string>)` API was completely removed in [PR #11514](https://github.com/esphome/esphome/pull/11514). Version guards are **not possible** because the old API no longer exists.
221+
222+
External components must either:
223+
- Update to the new API to support ESPHome 2025.11.0+
224+
- Pin to ESPHome versions before 2025.11.0 if they can't update yet
225+
226+
There is no way to support both old and new ESPHome versions for options storage without maintaining separate branches.
227+
228+
## Timeline
229+
230+
- **ESPHome 2025.11.0 (November 2025):**
231+
- Options storage change is active (breaking change)
232+
- `.state` member deprecated but still works with warnings
233+
- New `current_option()` method available
234+
235+
- **ESPHome 2026.5.0 (May 2026):**
236+
- `.state` member will be removed
237+
- Must use `current_option()` method
238+
239+
## Finding Code That Needs Updates
240+
241+
Search your external component code for these patterns:
242+
243+
```bash
244+
# Find .state member access
245+
grep -r '\.state' --include='*.cpp' --include='*.h'
246+
247+
# Find set_options() calls
248+
grep -r 'set_options' --include='*.cpp' --include='*.h'
249+
250+
# Find vector<string> option storage
251+
grep -r 'vector<.*string>' --include='*.cpp' --include='*.h'
252+
```
253+
254+
## Questions?
255+
256+
If you have questions about these changes or need help migrating your external component, please ask in the [ESPHome Discord](https://discord.gg/KhAMKrd) or open a [discussion on GitHub](https://github.com/esphome/esphome/discussions).
257+
258+
## Related Documentation
259+
260+
- [Select Component Documentation](https://esphome.io/components/select/index.html)
261+
- [PR #11623: Index-Based Operations](https://github.com/esphome/esphome/pull/11623)
262+
- [PR #11514: Store Options in Flash](https://github.com/esphome/esphome/pull/11514)

0 commit comments

Comments
 (0)