ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
homeassistant_select.cpp
Go to the documentation of this file.
2
3#include <cstring>
4
5#include "../homeassistant.h"
6
10#include "esphome/core/log.h"
13
14namespace esphome::homeassistant {
15
16ESPHOME_LOG_TAG(TAG, "homeassistant.select");
17
19 this->options_buffer_ = std::make_unique<char[]>(this->options_buffer_size_);
20 this->option_list_ = std::make_unique<const char *[]>(this->max_options_);
21
22 // Subscribe to the options first: Home Assistant answers subscriptions in order, so the options are
23 // known by the time the first state arrives.
25 this->entity_id_, "options", [this](StringRef options) { this->options_changed_(options); });
27 [this](StringRef state) { this->state_changed_(state); });
28}
29
31 // First pass: validate and measure the new options, and look for the active option among them, before
32 // anything is overwritten. A list that does not fit leaves the current options in place.
33 const char *active = this->has_state() ? this->option_at(this->active_index_) : nullptr;
34 size_t count = 0;
35 size_t bytes = 0;
36 size_t match_len = 0;
37 bool matching = active != nullptr;
38 optional<size_t> new_active;
40 options,
41 [&](char c) {
42 bytes++;
43 if (matching && active[match_len] == c) {
44 match_len++;
45 } else {
46 matching = false;
47 }
48 },
49 [&]() {
50 bytes++; // NUL terminator
51 if (matching && active[match_len] == '\0' && !new_active.has_value())
52 new_active = count;
53 count++;
54 match_len = 0;
55 matching = active != nullptr;
56 });
57 if (!valid) {
58 ESP_LOGW(TAG, "'%s': Can't parse options %s", this->entity_id_, options.c_str());
59 return;
60 }
61 if (count > this->max_options_) {
62 ESP_LOGE(TAG, "'%s': %zu options exceed max_options (%u)", this->entity_id_, count, this->max_options_);
63 return;
64 }
65 if (bytes > this->options_buffer_size_) {
66 ESP_LOGE(TAG, "'%s': Options need %zu bytes, more than options_buffer_size (%u)", this->entity_id_, bytes,
67 this->options_buffer_size_);
68 return;
69 }
70
71 // Second pass: the input is known to be valid and to fit, so write it out
72 char *buffer = this->options_buffer_.get();
73 size_t offset = 0;
74 size_t option_start = 0;
75 size_t index = 0;
77 options, [&](char c) { buffer[offset++] = c; },
78 [&]() {
79 buffer[offset++] = '\0';
80 this->option_list_[index++] = buffer + option_start;
81 option_start = offset;
82 });
83 // The traits only point at the list, which this select owns; set_options() would copy it to the heap
84 this->traits.set_options_static(this->option_list_.get(), count);
85
86 // The active option may have moved to another index, or may be gone
87 if (new_active.has_value()) {
88 this->active_index_ = *new_active;
89 } else {
90 this->set_has_state(false);
91 }
92 ESP_LOGD(TAG, "'%s': Got %zu options", this->entity_id_, count);
93}
94
96 auto index = this->index_of(state.c_str(), state.size());
97 if (!index.has_value()) {
98 ESP_LOGW(TAG, "'%s': State '%s' is not one of the options", this->entity_id_, state.c_str());
99 return;
100 }
101 if (this->has_state() && this->active_index_ == *index)
102 return;
103 ESP_LOGD(TAG, "'%s': Got state '%s'", this->entity_id_, state.c_str());
104 this->publish_state(*index);
105}
106
108 LOG_SELECT("", "Homeassistant Select", this);
109 ESP_LOGCONFIG(TAG,
110 " Entity ID: '%s'\n"
111 " Max options: %u\n"
112 " Options buffer size: %u",
113 this->entity_id_, this->max_options_, this->options_buffer_size_);
114}
115
117
119 if (!api::global_api_server->is_connected()) {
120 ESP_LOGE(TAG, "No clients connected to API server");
121 return;
122 }
123
124#ifdef USE_API_WIZARD_LINKED_INPUTS
125 if (this->entity_id_[0] == '\0') {
126 ESP_LOGW(TAG, "'%s': No entity ID set yet", this->get_name().c_str());
127 return;
128 }
129#endif
130
131 // In flash on the ESP8266 and encoded from there
132 static const char SERVICE_SELECT[] PROGMEM = "select.select_option";
133 static const char SERVICE_INPUT_SELECT[] PROGMEM = "input_select.select_option";
134 static const char ENTITY_ID_KEY[] PROGMEM = "entity_id";
135 static const char OPTION_KEY[] PROGMEM = "option";
136
138 // An input helper entity id starts with the "input_" its action name starts with
139 const bool input_helper = ESPHOME_strncmp_P(this->entity_id_, SERVICE_INPUT_SELECT, sizeof("input_") - 1) == 0;
140 resp.service =
141 input_helper ? ProgmemStringRef::from_lit(SERVICE_INPUT_SELECT) : ProgmemStringRef::from_lit(SERVICE_SELECT);
142
143 resp.data.init(2);
144 auto &entity_id = resp.data.emplace_back();
145 entity_id.key = ProgmemStringRef::from_lit(ENTITY_ID_KEY);
146 entity_id.value = ProgmemStringRef(StringRef(this->entity_id_));
147
148 auto &option = resp.data.emplace_back();
149 option.key = ProgmemStringRef::from_lit(OPTION_KEY);
150 option.value = ProgmemStringRef(StringRef(this->option_at(index)));
151
153}
154
155} // namespace esphome::homeassistant
const StringRef & get_name() const
Definition entity_base.h:71
void set_has_state(bool state)
bool has_state() const
A string that may live in flash (on ESP8266): pointer and length, with no byte-reading operations.
Definition string_ref.h:230
static constexpr ProgmemStringRef from_lit(const char(&s)[N])
A PROGMEM char array, with its length known at compile time.
Definition string_ref.h:236
StringRef is a reference to a string owned by something else.
Definition string_ref.h:31
constexpr const char * c_str() const
Definition string_ref.h:78
void send_homeassistant_action(const HomeassistantActionRequest &call)
void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function< void(StringRef)> &&f)
FixedVector< HomeassistantServiceMap > data
Definition api_pb2.h:1414
const char * option_at(size_t index) const
Return the option value at the provided index offset (as const char* from flash).
Definition select.cpp:60
optional< size_t > index_of(const char *option, size_t len) const
Find the (optional) index offset of the provided option value.
Definition select.cpp:35
SelectTraits traits
Definition select.h:31
void publish_state(const std::string &state)
Definition select.h:36
void set_options_static(const char *const *options, size_t count)
Codegen only: points at a table that outlives the select.
uint8_t options
bool state
Definition fan.h:2
unsigned count
Definition gif.cpp:51
uint32_t buffer
Definition gif.cpp:50
APIServer * global_api_server
bool parse_python_list(StringRef src, C &&on_char, O &&on_item)
Walk a list attribute as Home Assistant sends it: the Python repr of a list of strings,...
ESPHOME_LOG_TAG(TAG, "homeassistant.binary_sensor")
constexpr float AFTER_CONNECTION
For components that should be initialized after a data connection (API/MQTT) is connected.
Definition component.h:57
bool valid