mirror of
https://github.com/esphome/esphome.git
synced 2024-11-10 01:07:45 +01:00
Select enhancement (#3423)
Co-authored-by: Maurice Makaay <mmakaay1@xs4all.net>
This commit is contained in:
parent
3a3d97dfa7
commit
44b68f140e
20 changed files with 461 additions and 78 deletions
|
@ -64,6 +64,7 @@ from esphome.cpp_types import ( # noqa
|
||||||
uint64,
|
uint64,
|
||||||
int32,
|
int32,
|
||||||
int64,
|
int64,
|
||||||
|
size_t,
|
||||||
const_char_ptr,
|
const_char_ptr,
|
||||||
NAN,
|
NAN,
|
||||||
esphome_ns,
|
esphome_ns,
|
||||||
|
|
|
@ -255,7 +255,7 @@ void APIServer::on_number_update(number::Number *obj, float state) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_SELECT
|
#ifdef USE_SELECT
|
||||||
void APIServer::on_select_update(select::Select *obj, const std::string &state) {
|
void APIServer::on_select_update(select::Select *obj, const std::string &state, size_t index) {
|
||||||
if (obj->is_internal())
|
if (obj->is_internal())
|
||||||
return;
|
return;
|
||||||
for (auto &c : this->clients_)
|
for (auto &c : this->clients_)
|
||||||
|
|
|
@ -64,7 +64,7 @@ class APIServer : public Component, public Controller {
|
||||||
void on_number_update(number::Number *obj, float state) override;
|
void on_number_update(number::Number *obj, float state) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SELECT
|
#ifdef USE_SELECT
|
||||||
void on_select_update(select::Select *obj, const std::string &state) override;
|
void on_select_update(select::Select *obj, const std::string &state, size_t index) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_LOCK
|
#ifdef USE_LOCK
|
||||||
void on_lock_update(lock::Lock *obj) override;
|
void on_lock_update(lock::Lock *obj) override;
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace copy {
|
||||||
static const char *const TAG = "copy.select";
|
static const char *const TAG = "copy.select";
|
||||||
|
|
||||||
void CopySelect::setup() {
|
void CopySelect::setup() {
|
||||||
source_->add_on_state_callback([this](const std::string &value) { this->publish_state(value); });
|
source_->add_on_state_callback([this](const std::string &value, size_t index) { this->publish_state(value); });
|
||||||
|
|
||||||
traits.set_options(source_->traits.get_options());
|
traits.set_options(source_->traits.get_options());
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ void MQTTSelectComponent::setup() {
|
||||||
call.set_option(state);
|
call.set_option(state);
|
||||||
call.perform();
|
call.perform();
|
||||||
});
|
});
|
||||||
this->select_->add_on_state_callback([this](const std::string &state) { this->publish_state(state); });
|
this->select_->add_on_state_callback([this](const std::string &state, size_t index) { this->publish_state(state); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void MQTTSelectComponent::dump_config() {
|
void MQTTSelectComponent::dump_config() {
|
||||||
|
|
|
@ -9,6 +9,10 @@ from esphome.const import (
|
||||||
CONF_OPTION,
|
CONF_OPTION,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_MQTT_ID,
|
CONF_MQTT_ID,
|
||||||
|
CONF_CYCLE,
|
||||||
|
CONF_MODE,
|
||||||
|
CONF_OPERATION,
|
||||||
|
CONF_INDEX,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.cpp_helpers import setup_entity
|
from esphome.cpp_helpers import setup_entity
|
||||||
|
@ -22,14 +26,27 @@ SelectPtr = Select.operator("ptr")
|
||||||
|
|
||||||
# Triggers
|
# Triggers
|
||||||
SelectStateTrigger = select_ns.class_(
|
SelectStateTrigger = select_ns.class_(
|
||||||
"SelectStateTrigger", automation.Trigger.template(cg.float_)
|
"SelectStateTrigger",
|
||||||
|
automation.Trigger.template(cg.std_string, cg.size_t),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Actions
|
# Actions
|
||||||
SelectSetAction = select_ns.class_("SelectSetAction", automation.Action)
|
SelectSetAction = select_ns.class_("SelectSetAction", automation.Action)
|
||||||
|
SelectSetIndexAction = select_ns.class_("SelectSetIndexAction", automation.Action)
|
||||||
|
SelectOperationAction = select_ns.class_("SelectOperationAction", automation.Action)
|
||||||
|
|
||||||
|
# Enums
|
||||||
|
SelectOperation = select_ns.enum("SelectOperation")
|
||||||
|
SELECT_OPERATION_OPTIONS = {
|
||||||
|
"NEXT": SelectOperation.SELECT_OP_NEXT,
|
||||||
|
"PREVIOUS": SelectOperation.SELECT_OP_PREVIOUS,
|
||||||
|
"FIRST": SelectOperation.SELECT_OP_FIRST,
|
||||||
|
"LAST": SelectOperation.SELECT_OP_LAST,
|
||||||
|
}
|
||||||
|
|
||||||
icon = cv.icon
|
icon = cv.icon
|
||||||
|
|
||||||
|
|
||||||
SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||||
{
|
{
|
||||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent),
|
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent),
|
||||||
|
@ -50,7 +67,9 @@ async def setup_select_core_(var, config, *, options: List[str]):
|
||||||
|
|
||||||
for conf in config.get(CONF_ON_VALUE, []):
|
for conf in config.get(CONF_ON_VALUE, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
|
await automation.build_automation(
|
||||||
|
trigger, [(cg.std_string, "x"), (cg.size_t, "i")], conf
|
||||||
|
)
|
||||||
|
|
||||||
if CONF_MQTT_ID in config:
|
if CONF_MQTT_ID in config:
|
||||||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||||
|
@ -76,12 +95,18 @@ async def to_code(config):
|
||||||
cg.add_global(select_ns.using)
|
cg.add_global(select_ns.using)
|
||||||
|
|
||||||
|
|
||||||
|
OPERATION_BASE_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_ID): cv.use_id(Select),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@automation.register_action(
|
@automation.register_action(
|
||||||
"select.set",
|
"select.set",
|
||||||
SelectSetAction,
|
SelectSetAction,
|
||||||
cv.Schema(
|
OPERATION_BASE_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_ID): cv.use_id(Select),
|
|
||||||
cv.Required(CONF_OPTION): cv.templatable(cv.string_strict),
|
cv.Required(CONF_OPTION): cv.templatable(cv.string_strict),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
@ -92,3 +117,96 @@ async def select_set_to_code(config, action_id, template_arg, args):
|
||||||
template_ = await cg.templatable(config[CONF_OPTION], args, cg.std_string)
|
template_ = await cg.templatable(config[CONF_OPTION], args, cg.std_string)
|
||||||
cg.add(var.set_option(template_))
|
cg.add(var.set_option(template_))
|
||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"select.set_index",
|
||||||
|
SelectSetIndexAction,
|
||||||
|
OPERATION_BASE_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_INDEX): cv.templatable(cv.positive_int),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async def select_set_index_to_code(config, action_id, template_arg, args):
|
||||||
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
template_ = await cg.templatable(config[CONF_INDEX], args, cg.size_t)
|
||||||
|
cg.add(var.set_index(template_))
|
||||||
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"select.operation",
|
||||||
|
SelectOperationAction,
|
||||||
|
OPERATION_BASE_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_OPERATION): cv.templatable(
|
||||||
|
cv.enum(SELECT_OPERATION_OPTIONS, upper=True)
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_CYCLE, default=True): cv.templatable(cv.boolean),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@automation.register_action(
|
||||||
|
"select.next",
|
||||||
|
SelectOperationAction,
|
||||||
|
automation.maybe_simple_id(
|
||||||
|
OPERATION_BASE_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_MODE, default="NEXT"): cv.one_of("NEXT", upper=True),
|
||||||
|
cv.Optional(CONF_CYCLE, default=True): cv.boolean,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@automation.register_action(
|
||||||
|
"select.previous",
|
||||||
|
SelectOperationAction,
|
||||||
|
automation.maybe_simple_id(
|
||||||
|
OPERATION_BASE_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_MODE, default="PREVIOUS"): cv.one_of(
|
||||||
|
"PREVIOUS", upper=True
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_CYCLE, default=True): cv.boolean,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@automation.register_action(
|
||||||
|
"select.first",
|
||||||
|
SelectOperationAction,
|
||||||
|
automation.maybe_simple_id(
|
||||||
|
OPERATION_BASE_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_MODE, default="FIRST"): cv.one_of("FIRST", upper=True),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@automation.register_action(
|
||||||
|
"select.last",
|
||||||
|
SelectOperationAction,
|
||||||
|
automation.maybe_simple_id(
|
||||||
|
OPERATION_BASE_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_MODE, default="LAST"): cv.one_of("LAST", upper=True),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async def select_operation_to_code(config, action_id, template_arg, args):
|
||||||
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
if CONF_OPERATION in config:
|
||||||
|
op_ = await cg.templatable(config[CONF_OPERATION], args, SelectOperation)
|
||||||
|
cg.add(var.set_operation(op_))
|
||||||
|
if CONF_CYCLE in config:
|
||||||
|
cycle_ = await cg.templatable(config[CONF_CYCLE], args, bool)
|
||||||
|
cg.add(var.set_cycle(cycle_))
|
||||||
|
if CONF_MODE in config:
|
||||||
|
cg.add(var.set_operation(SELECT_OPERATION_OPTIONS[config[CONF_MODE]]))
|
||||||
|
if CONF_CYCLE in config:
|
||||||
|
cg.add(var.set_cycle(config[CONF_CYCLE]))
|
||||||
|
return var
|
||||||
|
|
|
@ -7,16 +7,16 @@
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace select {
|
namespace select {
|
||||||
|
|
||||||
class SelectStateTrigger : public Trigger<std::string> {
|
class SelectStateTrigger : public Trigger<std::string, size_t> {
|
||||||
public:
|
public:
|
||||||
explicit SelectStateTrigger(Select *parent) {
|
explicit SelectStateTrigger(Select *parent) {
|
||||||
parent->add_on_state_callback([this](const std::string &value) { this->trigger(value); });
|
parent->add_on_state_callback([this](const std::string &value, size_t index) { this->trigger(value, index); });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class SelectSetAction : public Action<Ts...> {
|
template<typename... Ts> class SelectSetAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
SelectSetAction(Select *select) : select_(select) {}
|
explicit SelectSetAction(Select *select) : select_(select) {}
|
||||||
TEMPLATABLE_VALUE(std::string, option)
|
TEMPLATABLE_VALUE(std::string, option)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(Ts... x) override {
|
||||||
|
@ -29,5 +29,39 @@ template<typename... Ts> class SelectSetAction : public Action<Ts...> {
|
||||||
Select *select_;
|
Select *select_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class SelectSetIndexAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
explicit SelectSetIndexAction(Select *select) : select_(select) {}
|
||||||
|
TEMPLATABLE_VALUE(size_t, index)
|
||||||
|
|
||||||
|
void play(Ts... x) override {
|
||||||
|
auto call = this->select_->make_call();
|
||||||
|
call.set_index(this->index_.value(x...));
|
||||||
|
call.perform();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Select *select_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class SelectOperationAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
explicit SelectOperationAction(Select *select) : select_(select) {}
|
||||||
|
TEMPLATABLE_VALUE(bool, cycle)
|
||||||
|
TEMPLATABLE_VALUE(SelectOperation, operation)
|
||||||
|
|
||||||
|
void play(Ts... x) override {
|
||||||
|
auto call = this->select_->make_call();
|
||||||
|
call.with_operation(this->operation_.value(x...));
|
||||||
|
if (this->cycle_.has_value()) {
|
||||||
|
call.with_cycle(this->cycle_.value(x...));
|
||||||
|
}
|
||||||
|
call.perform();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Select *select_;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace select
|
} // namespace select
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -6,37 +6,53 @@ namespace select {
|
||||||
|
|
||||||
static const char *const TAG = "select";
|
static const char *const TAG = "select";
|
||||||
|
|
||||||
void SelectCall::perform() {
|
|
||||||
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
|
||||||
if (!this->option_.has_value()) {
|
|
||||||
ESP_LOGW(TAG, "No value set for SelectCall");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto &traits = this->parent_->traits;
|
|
||||||
auto value = *this->option_;
|
|
||||||
auto options = traits.get_options();
|
|
||||||
|
|
||||||
if (std::find(options.begin(), options.end(), value) == options.end()) {
|
|
||||||
ESP_LOGW(TAG, " Option %s is not a valid option.", value.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, " Option: %s", (*this->option_).c_str());
|
|
||||||
this->parent_->control(*this->option_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Select::publish_state(const std::string &state) {
|
void Select::publish_state(const std::string &state) {
|
||||||
this->has_state_ = true;
|
auto index = this->index_of(state);
|
||||||
this->state = state;
|
const auto *name = this->get_name().c_str();
|
||||||
ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), state.c_str());
|
if (index.has_value()) {
|
||||||
this->state_callback_.call(state);
|
this->has_state_ = true;
|
||||||
|
this->state = state;
|
||||||
|
ESP_LOGD(TAG, "'%s': Sending state %s (index %d)", name, state.c_str(), index.value());
|
||||||
|
this->state_callback_.call(state, index.value());
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "'%s': invalid state for publish_state(): %s", name, state.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Select::add_on_state_callback(std::function<void(std::string)> &&callback) {
|
void Select::add_on_state_callback(std::function<void(std::string, size_t)> &&callback) {
|
||||||
this->state_callback_.add(std::move(callback));
|
this->state_callback_.add(std::move(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t Select::size() const {
|
||||||
|
auto options = traits.get_options();
|
||||||
|
return options.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
optional<size_t> Select::index_of(const std::string &option) const {
|
||||||
|
auto options = traits.get_options();
|
||||||
|
auto it = std::find(options.begin(), options.end(), option);
|
||||||
|
if (it == options.end()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return std::distance(options.begin(), it);
|
||||||
|
}
|
||||||
|
|
||||||
|
optional<size_t> Select::active_index() const {
|
||||||
|
if (this->has_state()) {
|
||||||
|
return this->index_of(this->state);
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
optional<std::string> Select::at(size_t index) const {
|
||||||
|
auto options = traits.get_options();
|
||||||
|
if (index >= options.size()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return options.at(index);
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t Select::hash_base() { return 2812997003UL; }
|
uint32_t Select::hash_base() { return 2812997003UL; }
|
||||||
|
|
||||||
} // namespace select
|
} // namespace select
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <set>
|
|
||||||
#include <utility>
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/entity_base.h"
|
#include "esphome/core/entity_base.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "select_call.h"
|
||||||
|
#include "select_traits.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace select {
|
namespace select {
|
||||||
|
@ -17,33 +17,6 @@ namespace select {
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
class Select;
|
|
||||||
|
|
||||||
class SelectCall {
|
|
||||||
public:
|
|
||||||
explicit SelectCall(Select *parent) : parent_(parent) {}
|
|
||||||
void perform();
|
|
||||||
|
|
||||||
SelectCall &set_option(const std::string &option) {
|
|
||||||
option_ = option;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
const optional<std::string> &get_option() const { return option_; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
Select *const parent_;
|
|
||||||
optional<std::string> option_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SelectTraits {
|
|
||||||
public:
|
|
||||||
void set_options(std::vector<std::string> options) { this->options_ = std::move(options); }
|
|
||||||
std::vector<std::string> get_options() const { return this->options_; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::vector<std::string> options_;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Base-class for all selects.
|
/** Base-class for all selects.
|
||||||
*
|
*
|
||||||
* A select can use publish_state to send out a new value.
|
* A select can use publish_state to send out a new value.
|
||||||
|
@ -51,18 +24,23 @@ class SelectTraits {
|
||||||
class Select : public EntityBase {
|
class Select : public EntityBase {
|
||||||
public:
|
public:
|
||||||
std::string state;
|
std::string state;
|
||||||
|
SelectTraits traits;
|
||||||
|
|
||||||
void publish_state(const std::string &state);
|
void publish_state(const std::string &state);
|
||||||
|
|
||||||
|
/// Return whether this select has gotten a full state yet.
|
||||||
|
bool has_state() const { return has_state_; }
|
||||||
|
|
||||||
SelectCall make_call() { return SelectCall(this); }
|
SelectCall make_call() { return SelectCall(this); }
|
||||||
void set(const std::string &value) { make_call().set_option(value).perform(); }
|
void set(const std::string &value) { make_call().set_option(value).perform(); }
|
||||||
|
|
||||||
void add_on_state_callback(std::function<void(std::string)> &&callback);
|
// Methods that provide an API to index-based access.
|
||||||
|
size_t size() const;
|
||||||
|
optional<size_t> index_of(const std::string &option) const;
|
||||||
|
optional<size_t> active_index() const;
|
||||||
|
optional<std::string> at(size_t index) const;
|
||||||
|
|
||||||
SelectTraits traits;
|
void add_on_state_callback(std::function<void(std::string, size_t)> &&callback);
|
||||||
|
|
||||||
/// Return whether this select has gotten a full state yet.
|
|
||||||
bool has_state() const { return has_state_; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class SelectCall;
|
friend class SelectCall;
|
||||||
|
@ -77,7 +55,7 @@ class Select : public EntityBase {
|
||||||
|
|
||||||
uint32_t hash_base() override;
|
uint32_t hash_base() override;
|
||||||
|
|
||||||
CallbackManager<void(std::string)> state_callback_;
|
CallbackManager<void(std::string, size_t)> state_callback_;
|
||||||
bool has_state_{false};
|
bool has_state_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
122
esphome/components/select/select_call.cpp
Normal file
122
esphome/components/select/select_call.cpp
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
#include "select_call.h"
|
||||||
|
#include "select.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace select {
|
||||||
|
|
||||||
|
static const char *const TAG = "select";
|
||||||
|
|
||||||
|
SelectCall &SelectCall::set_option(const std::string &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); }
|
||||||
|
|
||||||
|
const optional<std::string> &SelectCall::get_option() const { return option_; }
|
||||||
|
|
||||||
|
SelectCall &SelectCall::select_next(bool cycle) { return with_operation(SELECT_OP_NEXT).with_cycle(cycle); }
|
||||||
|
|
||||||
|
SelectCall &SelectCall::select_previous(bool cycle) { return with_operation(SELECT_OP_PREVIOUS).with_cycle(cycle); }
|
||||||
|
|
||||||
|
SelectCall &SelectCall::select_first() { return with_operation(SELECT_OP_FIRST); }
|
||||||
|
|
||||||
|
SelectCall &SelectCall::select_last() { return with_operation(SELECT_OP_LAST); }
|
||||||
|
|
||||||
|
SelectCall &SelectCall::with_operation(SelectOperation operation) {
|
||||||
|
this->operation_ = operation;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectCall &SelectCall::with_cycle(bool cycle) {
|
||||||
|
this->cycle_ = cycle;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectCall &SelectCall::with_option(const std::string &option) {
|
||||||
|
this->option_ = option;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectCall &SelectCall::with_index(size_t index) {
|
||||||
|
this->index_ = index;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SelectCall::perform() {
|
||||||
|
auto *parent = this->parent_;
|
||||||
|
const auto *name = parent->get_name().c_str();
|
||||||
|
const auto &traits = parent->traits;
|
||||||
|
auto options = traits.get_options();
|
||||||
|
|
||||||
|
if (this->operation_ == SELECT_OP_NONE) {
|
||||||
|
ESP_LOGW(TAG, "'%s' - SelectCall performed without selecting an operation", name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (options.empty()) {
|
||||||
|
ESP_LOGW(TAG, "'%s' - Cannot perform SelectCall, select has no options", name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string target_value;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this->index_.value() >= options.size()) {
|
||||||
|
ESP_LOGW(TAG, "'%s' - Index value %d out of bounds", name, this->index_.value());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
target_value = options[this->index_.value()];
|
||||||
|
} else if (this->operation_ == SELECT_OP_FIRST) {
|
||||||
|
target_value = options.front();
|
||||||
|
} else if (this->operation_ == SELECT_OP_LAST) {
|
||||||
|
target_value = options.back();
|
||||||
|
} 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();
|
||||||
|
} 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
target_value = this->operation_ == SELECT_OP_NEXT ? options.front() : options.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::find(options.begin(), options.end(), target_value) == options.end()) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace select
|
||||||
|
} // namespace esphome
|
48
esphome/components/select/select_call.h
Normal file
48
esphome/components/select/select_call.h
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace select {
|
||||||
|
|
||||||
|
class Select;
|
||||||
|
|
||||||
|
enum SelectOperation {
|
||||||
|
SELECT_OP_NONE,
|
||||||
|
SELECT_OP_SET,
|
||||||
|
SELECT_OP_SET_INDEX,
|
||||||
|
SELECT_OP_NEXT,
|
||||||
|
SELECT_OP_PREVIOUS,
|
||||||
|
SELECT_OP_FIRST,
|
||||||
|
SELECT_OP_LAST
|
||||||
|
};
|
||||||
|
|
||||||
|
class SelectCall {
|
||||||
|
public:
|
||||||
|
explicit SelectCall(Select *parent) : parent_(parent) {}
|
||||||
|
void perform();
|
||||||
|
|
||||||
|
SelectCall &set_option(const std::string &option);
|
||||||
|
SelectCall &set_index(size_t index);
|
||||||
|
const optional<std::string> &get_option() const;
|
||||||
|
|
||||||
|
SelectCall &select_next(bool cycle);
|
||||||
|
SelectCall &select_previous(bool cycle);
|
||||||
|
SelectCall &select_first();
|
||||||
|
SelectCall &select_last();
|
||||||
|
|
||||||
|
SelectCall &with_operation(SelectOperation operation);
|
||||||
|
SelectCall &with_cycle(bool cycle);
|
||||||
|
SelectCall &with_option(const std::string &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_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace select
|
||||||
|
} // namespace esphome
|
11
esphome/components/select/select_traits.cpp
Normal file
11
esphome/components/select/select_traits.cpp
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#include "select_traits.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace select {
|
||||||
|
|
||||||
|
void SelectTraits::set_options(std::vector<std::string> options) { this->options_ = std::move(options); }
|
||||||
|
|
||||||
|
std::vector<std::string> SelectTraits::get_options() const { return this->options_; }
|
||||||
|
|
||||||
|
} // namespace select
|
||||||
|
} // namespace esphome
|
19
esphome/components/select/select_traits.h
Normal file
19
esphome/components/select/select_traits.h
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace select {
|
||||||
|
|
||||||
|
class SelectTraits {
|
||||||
|
public:
|
||||||
|
void set_options(std::vector<std::string> options);
|
||||||
|
std::vector<std::string> get_options() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<std::string> options_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace select
|
||||||
|
} // namespace esphome
|
|
@ -755,7 +755,7 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_SELECT
|
#ifdef USE_SELECT
|
||||||
void WebServer::on_select_update(select::Select *obj, const std::string &state) {
|
void WebServer::on_select_update(select::Select *obj, const std::string &state, size_t index) {
|
||||||
this->events_.send(this->select_json(obj, state, DETAIL_STATE).c_str(), "state");
|
this->events_.send(this->select_json(obj, state, DETAIL_STATE).c_str(), "state");
|
||||||
}
|
}
|
||||||
void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||||
|
|
|
@ -185,7 +185,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_SELECT
|
#ifdef USE_SELECT
|
||||||
void on_select_update(select::Select *obj, const std::string &state) override;
|
void on_select_update(select::Select *obj, const std::string &state, size_t index) override;
|
||||||
/// Handle a select request under '/select/<id>'.
|
/// Handle a select request under '/select/<id>'.
|
||||||
void handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
void handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||||
|
|
||||||
|
|
|
@ -138,6 +138,7 @@ CONF_CUSTOM_FAN_MODE = "custom_fan_mode"
|
||||||
CONF_CUSTOM_FAN_MODES = "custom_fan_modes"
|
CONF_CUSTOM_FAN_MODES = "custom_fan_modes"
|
||||||
CONF_CUSTOM_PRESET = "custom_preset"
|
CONF_CUSTOM_PRESET = "custom_preset"
|
||||||
CONF_CUSTOM_PRESETS = "custom_presets"
|
CONF_CUSTOM_PRESETS = "custom_presets"
|
||||||
|
CONF_CYCLE = "cycle"
|
||||||
CONF_DALLAS_ID = "dallas_id"
|
CONF_DALLAS_ID = "dallas_id"
|
||||||
CONF_DATA = "data"
|
CONF_DATA = "data"
|
||||||
CONF_DATA_PIN = "data_pin"
|
CONF_DATA_PIN = "data_pin"
|
||||||
|
@ -458,6 +459,7 @@ CONF_OPEN_DRAIN = "open_drain"
|
||||||
CONF_OPEN_DRAIN_INTERRUPT = "open_drain_interrupt"
|
CONF_OPEN_DRAIN_INTERRUPT = "open_drain_interrupt"
|
||||||
CONF_OPEN_DURATION = "open_duration"
|
CONF_OPEN_DURATION = "open_duration"
|
||||||
CONF_OPEN_ENDSTOP = "open_endstop"
|
CONF_OPEN_ENDSTOP = "open_endstop"
|
||||||
|
CONF_OPERATION = "operation"
|
||||||
CONF_OPTIMISTIC = "optimistic"
|
CONF_OPTIMISTIC = "optimistic"
|
||||||
CONF_OPTION = "option"
|
CONF_OPTION = "option"
|
||||||
CONF_OPTIONS = "options"
|
CONF_OPTIONS = "options"
|
||||||
|
|
|
@ -61,8 +61,10 @@ void Controller::setup_controller(bool include_internal) {
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SELECT
|
#ifdef USE_SELECT
|
||||||
for (auto *obj : App.get_selects()) {
|
for (auto *obj : App.get_selects()) {
|
||||||
if (include_internal || !obj->is_internal())
|
if (include_internal || !obj->is_internal()) {
|
||||||
obj->add_on_state_callback([this, obj](const std::string &state) { this->on_select_update(obj, state); });
|
obj->add_on_state_callback(
|
||||||
|
[this, obj](const std::string &state, size_t index) { this->on_select_update(obj, state, index); });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_LOCK
|
#ifdef USE_LOCK
|
||||||
|
|
|
@ -71,7 +71,7 @@ class Controller {
|
||||||
virtual void on_number_update(number::Number *obj, float state){};
|
virtual void on_number_update(number::Number *obj, float state){};
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SELECT
|
#ifdef USE_SELECT
|
||||||
virtual void on_select_update(select::Select *obj, const std::string &state){};
|
virtual void on_select_update(select::Select *obj, const std::string &state, size_t index){};
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_LOCK
|
#ifdef USE_LOCK
|
||||||
virtual void on_lock_update(lock::Lock *obj){};
|
virtual void on_lock_update(lock::Lock *obj){};
|
||||||
|
|
|
@ -16,6 +16,7 @@ uint32 = global_ns.namespace("uint32_t")
|
||||||
uint64 = global_ns.namespace("uint64_t")
|
uint64 = global_ns.namespace("uint64_t")
|
||||||
int32 = global_ns.namespace("int32_t")
|
int32 = global_ns.namespace("int32_t")
|
||||||
int64 = global_ns.namespace("int64_t")
|
int64 = global_ns.namespace("int64_t")
|
||||||
|
size_t = global_ns.namespace("size_t")
|
||||||
const_char_ptr = global_ns.namespace("const char *")
|
const_char_ptr = global_ns.namespace("const char *")
|
||||||
NAN = global_ns.namespace("NAN")
|
NAN = global_ns.namespace("NAN")
|
||||||
esphome_ns = global_ns # using namespace esphome;
|
esphome_ns = global_ns # using namespace esphome;
|
||||||
|
|
|
@ -154,8 +154,8 @@ select:
|
||||||
restore_value: true
|
restore_value: true
|
||||||
on_value:
|
on_value:
|
||||||
- logger.log:
|
- logger.log:
|
||||||
format: "Select changed to %s"
|
format: "Select changed to %s (index %d)"
|
||||||
args: ["x.c_str()"]
|
args: ["x.c_str()", "i"]
|
||||||
set_action:
|
set_action:
|
||||||
- logger.log:
|
- logger.log:
|
||||||
format: "Template Select set to %s"
|
format: "Template Select set to %s"
|
||||||
|
@ -163,11 +163,42 @@ select:
|
||||||
- select.set:
|
- select.set:
|
||||||
id: template_select_id
|
id: template_select_id
|
||||||
option: two
|
option: two
|
||||||
|
- select.first: template_select_id
|
||||||
|
- select.last:
|
||||||
|
id: template_select_id
|
||||||
|
- select.previous: template_select_id
|
||||||
|
- select.next:
|
||||||
|
id: template_select_id
|
||||||
|
cycle: false
|
||||||
|
- select.operation:
|
||||||
|
id: template_select_id
|
||||||
|
operation: Previous
|
||||||
|
cycle: false
|
||||||
|
- select.operation:
|
||||||
|
id: template_select_id
|
||||||
|
operation: !lambda "return SELECT_OP_PREVIOUS;"
|
||||||
|
cycle: !lambda "return true;"
|
||||||
|
- select.set_index:
|
||||||
|
id: template_select_id
|
||||||
|
index: 1
|
||||||
|
- select.set_index:
|
||||||
|
id: template_select_id
|
||||||
|
index: !lambda "return 1 + 1;"
|
||||||
options:
|
options:
|
||||||
- one
|
- one
|
||||||
- two
|
- two
|
||||||
- three
|
- three
|
||||||
|
|
||||||
|
- platform: modbus_controller
|
||||||
|
name: "Modbus Select Register 1000"
|
||||||
|
address: 1000
|
||||||
|
value_type: U_WORD
|
||||||
|
optionsmap:
|
||||||
|
"Zero": 0
|
||||||
|
"One": 1
|
||||||
|
"Two": 2
|
||||||
|
"Three": 3
|
||||||
|
|
||||||
sensor:
|
sensor:
|
||||||
- platform: selec_meter
|
- platform: selec_meter
|
||||||
total_active_energy:
|
total_active_energy:
|
||||||
|
|
Loading…
Reference in a new issue