mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	select by index
This commit is contained in:
		| @@ -877,7 +877,7 @@ uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection | ||||
|                                               bool is_single) { | ||||
|   auto *select = static_cast<select::Select *>(entity); | ||||
|   SelectStateResponse resp; | ||||
|   resp.set_state(StringRef(select->state)); | ||||
|   resp.set_state(StringRef(select->current_option())); | ||||
|   resp.missing_state = !select->has_state(); | ||||
|   return fill_and_encode_entity_state(select, resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||
| } | ||||
|   | ||||
| @@ -3,10 +3,10 @@ | ||||
| namespace esphome::logger { | ||||
|  | ||||
| void LoggerLevelSelect::publish_state(int level) { | ||||
|   const auto &option = this->at(level_to_index(level)); | ||||
|   if (!option) | ||||
|   auto index = level_to_index(level); | ||||
|   if (!this->has_index(index)) | ||||
|     return; | ||||
|   Select::publish_state(option.value()); | ||||
|   Select::publish_state(index); | ||||
| } | ||||
|  | ||||
| void LoggerLevelSelect::setup() { | ||||
|   | ||||
| @@ -7,24 +7,39 @@ namespace select { | ||||
|  | ||||
| static const char *const TAG = "select"; | ||||
|  | ||||
| void Select::publish_state(const std::string &state) { | ||||
| void Select::publish_state(const std::string &state) { this->publish_state(state.c_str()); } | ||||
|  | ||||
| void Select::publish_state(const char *state) { | ||||
|   auto index = this->index_of(state); | ||||
|   const auto *name = this->get_name().c_str(); | ||||
|   if (index.has_value()) { | ||||
|     this->set_has_state(true); | ||||
|     this->state = state; | ||||
|     ESP_LOGD(TAG, "'%s': Sending state %s (index %zu)", name, state.c_str(), index.value()); | ||||
|     this->state_callback_.call(state, index.value()); | ||||
|     this->publish_state(index.value()); | ||||
|   } else { | ||||
|     ESP_LOGE(TAG, "'%s': invalid state for publish_state(): %s", name, state.c_str()); | ||||
|     ESP_LOGE(TAG, "'%s': invalid state for publish_state(): %s", this->get_name().c_str(), state); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void Select::publish_state(size_t index) { | ||||
|   if (!this->has_index(index)) { | ||||
|     ESP_LOGE(TAG, "'%s': invalid index for publish_state(): %zu", this->get_name().c_str(), index); | ||||
|     return; | ||||
|   } | ||||
|   const char *option = this->option_at(index); | ||||
|   this->set_has_state(true); | ||||
|   this->active_index_ = index; | ||||
|   ESP_LOGD(TAG, "'%s': Sending state %s (index %zu)", this->get_name().c_str(), option, index); | ||||
|   // Callback signature requires std::string, create temporary for compatibility | ||||
|   this->state_callback_.call(std::string(option), index); | ||||
| } | ||||
|  | ||||
| const char *Select::current_option() const { return this->option_at(this->active_index_); } | ||||
|  | ||||
| void Select::add_on_state_callback(std::function<void(std::string, size_t)> &&callback) { | ||||
|   this->state_callback_.add(std::move(callback)); | ||||
| } | ||||
|  | ||||
| bool Select::has_option(const std::string &option) const { return this->index_of(option).has_value(); } | ||||
| bool Select::has_option(const std::string &option) const { return this->index_of(option.c_str()).has_value(); } | ||||
|  | ||||
| bool Select::has_option(const char *option) const { return this->index_of(option).has_value(); } | ||||
|  | ||||
| bool Select::has_index(size_t index) const { return index < this->size(); } | ||||
|  | ||||
| @@ -33,10 +48,12 @@ size_t Select::size() const { | ||||
|   return options.size(); | ||||
| } | ||||
|  | ||||
| optional<size_t> Select::index_of(const std::string &option) const { | ||||
| optional<size_t> Select::index_of(const std::string &option) const { return this->index_of(option.c_str()); } | ||||
|  | ||||
| optional<size_t> Select::index_of(const char *option) const { | ||||
|   const auto &options = traits.get_options(); | ||||
|   for (size_t i = 0; i < options.size(); i++) { | ||||
|     if (strcmp(options[i], option.c_str()) == 0) { | ||||
|     if (strcmp(options[i], option) == 0) { | ||||
|       return i; | ||||
|     } | ||||
|   } | ||||
| @@ -45,7 +62,7 @@ optional<size_t> Select::index_of(const std::string &option) const { | ||||
|  | ||||
| optional<size_t> Select::active_index() const { | ||||
|   if (this->has_state()) { | ||||
|     return this->index_of(this->state); | ||||
|     return this->active_index_; | ||||
|   } else { | ||||
|     return {}; | ||||
|   } | ||||
|   | ||||
| @@ -30,16 +30,21 @@ namespace select { | ||||
|  */ | ||||
| class Select : public EntityBase { | ||||
|  public: | ||||
|   std::string state; | ||||
|   SelectTraits traits; | ||||
|  | ||||
|   void publish_state(const std::string &state); | ||||
|   void publish_state(const char *state); | ||||
|   void publish_state(size_t index); | ||||
|  | ||||
|   /// Return the currently selected option (as const char* from flash). | ||||
|   const char *current_option() const; | ||||
|  | ||||
|   /// Instantiate a SelectCall object to modify this select component's state. | ||||
|   SelectCall make_call() { return SelectCall(this); } | ||||
|  | ||||
|   /// Return whether this select component contains the provided option. | ||||
|   bool has_option(const std::string &option) const; | ||||
|   bool has_option(const char *option) const; | ||||
|  | ||||
|   /// Return whether this select component contains the provided index offset. | ||||
|   bool has_index(size_t index) const; | ||||
| @@ -49,6 +54,7 @@ class Select : public EntityBase { | ||||
|  | ||||
|   /// Find the (optional) index offset of the provided option value. | ||||
|   optional<size_t> index_of(const std::string &option) const; | ||||
|   optional<size_t> index_of(const char *option) const; | ||||
|  | ||||
|   /// Return the (optional) index offset of the currently active option. | ||||
|   optional<size_t> active_index() const; | ||||
| @@ -64,6 +70,18 @@ class Select : public EntityBase { | ||||
|  protected: | ||||
|   friend class SelectCall; | ||||
|  | ||||
|   size_t active_index_{0}; | ||||
|  | ||||
|   /** Set the value of the select by index, this is an optional virtual method. | ||||
|    * | ||||
|    * This method is called by the SelectCall when the index is already known. | ||||
|    * Default implementation converts to string and calls control(). | ||||
|    * Override this to work directly with indices and avoid string conversions. | ||||
|    * | ||||
|    * @param index The index as validated by the SelectCall. | ||||
|    */ | ||||
|   virtual void control(size_t index) { this->control(this->option_at(index)); } | ||||
|  | ||||
|   /** Set the value of the select, this is a virtual method that each select integration must implement. | ||||
|    * | ||||
|    * This method is called by the SelectCall. | ||||
|   | ||||
| @@ -11,6 +11,8 @@ SelectCall &SelectCall::set_option(const std::string &option) { | ||||
|   return with_operation(SELECT_OP_SET).with_option(option); | ||||
| } | ||||
|  | ||||
| SelectCall &SelectCall::set_option(const char *option) { return with_operation(SELECT_OP_SET).with_option(option); } | ||||
|  | ||||
| SelectCall &SelectCall::set_index(size_t index) { return with_operation(SELECT_OP_SET_INDEX).with_index(index); } | ||||
|  | ||||
| SelectCall &SelectCall::select_next(bool cycle) { return with_operation(SELECT_OP_NEXT).with_cycle(cycle); } | ||||
| @@ -31,8 +33,11 @@ SelectCall &SelectCall::with_cycle(bool cycle) { | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| SelectCall &SelectCall::with_option(const std::string &option) { | ||||
|   this->option_ = option; | ||||
| SelectCall &SelectCall::with_option(const std::string &option) { return this->with_option(option.c_str()); } | ||||
|  | ||||
| SelectCall &SelectCall::with_option(const char *option) { | ||||
|   // Find the option index - this validates the option exists | ||||
|   this->index_ = this->parent_->index_of(option); | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| @@ -56,64 +61,52 @@ void SelectCall::perform() { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   std::string target_value; | ||||
|   size_t target_index; | ||||
|  | ||||
|   if (this->operation_ == SELECT_OP_SET) { | ||||
|     ESP_LOGD(TAG, "'%s' - Setting", name); | ||||
|     if (!this->option_.has_value()) { | ||||
|       ESP_LOGW(TAG, "'%s' - No option value set for SelectCall", name); | ||||
|       return; | ||||
|   if (this->operation_ == SELECT_OP_SET || this->operation_ == SELECT_OP_SET_INDEX) { | ||||
|     if (this->operation_ == SELECT_OP_SET) { | ||||
|       ESP_LOGD(TAG, "'%s' - Setting", name); | ||||
|     } | ||||
|     target_value = this->option_.value(); | ||||
|   } else if (this->operation_ == SELECT_OP_SET_INDEX) { | ||||
|     if (!this->index_.has_value()) { | ||||
|       ESP_LOGW(TAG, "'%s' - No index value set for SelectCall", name); | ||||
|       ESP_LOGW(TAG, "'%s' - No option value set for SelectCall", name); | ||||
|       return; | ||||
|     } | ||||
|     if (this->index_.value() >= options.size()) { | ||||
|       ESP_LOGW(TAG, "'%s' - Index value %zu out of bounds", name, this->index_.value()); | ||||
|       return; | ||||
|     } | ||||
|     target_value = options[this->index_.value()]; | ||||
|     target_index = this->index_.value(); | ||||
|   } else if (this->operation_ == SELECT_OP_FIRST) { | ||||
|     target_value = options.front(); | ||||
|     target_index = 0; | ||||
|   } else if (this->operation_ == SELECT_OP_LAST) { | ||||
|     target_value = options.back(); | ||||
|     target_index = options.size() - 1; | ||||
|   } else if (this->operation_ == SELECT_OP_NEXT || this->operation_ == SELECT_OP_PREVIOUS) { | ||||
|     auto cycle = this->cycle_; | ||||
|     ESP_LOGD(TAG, "'%s' - Selecting %s, with%s cycling", name, this->operation_ == SELECT_OP_NEXT ? "next" : "previous", | ||||
|              cycle ? "" : "out"); | ||||
|     if (!parent->has_state()) { | ||||
|       target_value = this->operation_ == SELECT_OP_NEXT ? options.front() : options.back(); | ||||
|       target_index = this->operation_ == SELECT_OP_NEXT ? 0 : options.size() - 1; | ||||
|     } else { | ||||
|       auto index = parent->index_of(parent->state); | ||||
|       if (index.has_value()) { | ||||
|         auto size = options.size(); | ||||
|         if (cycle) { | ||||
|           auto use_index = (size + index.value() + (this->operation_ == SELECT_OP_NEXT ? +1 : -1)) % size; | ||||
|           target_value = options[use_index]; | ||||
|         } else { | ||||
|           if (this->operation_ == SELECT_OP_PREVIOUS && index.value() > 0) { | ||||
|             target_value = options[index.value() - 1]; | ||||
|           } else if (this->operation_ == SELECT_OP_NEXT && index.value() < options.size() - 1) { | ||||
|             target_value = options[index.value() + 1]; | ||||
|           } else { | ||||
|             return; | ||||
|           } | ||||
|         } | ||||
|       // Use cached active_index_ instead of index_of() lookup | ||||
|       auto index = parent->active_index_; | ||||
|       auto size = options.size(); | ||||
|       if (cycle) { | ||||
|         target_index = (size + index + (this->operation_ == SELECT_OP_NEXT ? +1 : -1)) % size; | ||||
|       } else { | ||||
|         target_value = this->operation_ == SELECT_OP_NEXT ? options.front() : options.back(); | ||||
|         if (this->operation_ == SELECT_OP_PREVIOUS && index > 0) { | ||||
|           target_index = index - 1; | ||||
|         } else if (this->operation_ == SELECT_OP_NEXT && index < options.size() - 1) { | ||||
|           target_index = index + 1; | ||||
|         } else { | ||||
|           return; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (!parent->has_option(target_value)) { | ||||
|     ESP_LOGW(TAG, "'%s' - Option %s is not a valid option", name, target_value.c_str()); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   ESP_LOGD(TAG, "'%s' - Set selected option to: %s", name, target_value.c_str()); | ||||
|   parent->control(target_value); | ||||
|   // All operations use indices, call control() by index to avoid string conversion | ||||
|   ESP_LOGD(TAG, "'%s' - Set selected option to: %s", name, options[target_index]); | ||||
|   parent->control(target_index); | ||||
| } | ||||
|  | ||||
| }  // namespace select | ||||
|   | ||||
| @@ -23,6 +23,7 @@ class SelectCall { | ||||
|   void perform(); | ||||
|  | ||||
|   SelectCall &set_option(const std::string &option); | ||||
|   SelectCall &set_option(const char *option); | ||||
|   SelectCall &set_index(size_t index); | ||||
|  | ||||
|   SelectCall &select_next(bool cycle); | ||||
| @@ -33,11 +34,11 @@ class SelectCall { | ||||
|   SelectCall &with_operation(SelectOperation operation); | ||||
|   SelectCall &with_cycle(bool cycle); | ||||
|   SelectCall &with_option(const std::string &option); | ||||
|   SelectCall &with_option(const char *option); | ||||
|   SelectCall &with_index(size_t index); | ||||
|  | ||||
|  protected: | ||||
|   Select *const parent_; | ||||
|   optional<std::string> option_; | ||||
|   optional<size_t> index_; | ||||
|   SelectOperation operation_{SELECT_OP_NONE}; | ||||
|   bool cycle_; | ||||
|   | ||||
| @@ -24,7 +24,7 @@ void TemplateSelect::setup() { | ||||
|     ESP_LOGD(TAG, "State from initial: %s", this->option_at(index)); | ||||
|   } | ||||
|  | ||||
|   this->publish_state(this->at(index).value()); | ||||
|   this->publish_state(index); | ||||
| } | ||||
|  | ||||
| void TemplateSelect::update() { | ||||
|   | ||||
| @@ -17,8 +17,7 @@ void TuyaSelect::setup() { | ||||
|       return; | ||||
|     } | ||||
|     size_t mapping_idx = std::distance(mappings.cbegin(), it); | ||||
|     auto value = this->at(mapping_idx); | ||||
|     this->publish_state(value.value()); | ||||
|     this->publish_state(mapping_idx); | ||||
|   }); | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user