Number enhancement (#3429)

Co-authored-by: Maurice Makaay <mmakaay1@xs4all.net>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
Maurice Makaay 2022-05-10 06:58:56 +02:00 committed by GitHub
parent 44b68f140e
commit d9caab4108
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 380 additions and 88 deletions

View file

@ -14,6 +14,8 @@ from esphome.const import (
CONF_UNIT_OF_MEASUREMENT, CONF_UNIT_OF_MEASUREMENT,
CONF_MQTT_ID, CONF_MQTT_ID,
CONF_VALUE, CONF_VALUE,
CONF_OPERATION,
CONF_CYCLE,
) )
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
@ -35,6 +37,7 @@ ValueRangeTrigger = number_ns.class_(
# Actions # Actions
NumberSetAction = number_ns.class_("NumberSetAction", automation.Action) NumberSetAction = number_ns.class_("NumberSetAction", automation.Action)
NumberOperationAction = number_ns.class_("NumberOperationAction", automation.Action)
# Conditions # Conditions
NumberInRangeCondition = number_ns.class_( NumberInRangeCondition = number_ns.class_(
@ -49,6 +52,15 @@ NUMBER_MODES = {
"SLIDER": NumberMode.NUMBER_MODE_SLIDER, "SLIDER": NumberMode.NUMBER_MODE_SLIDER,
} }
NumberOperation = number_ns.enum("NumberOperation")
NUMBER_OPERATION_OPTIONS = {
"INCREMENT": NumberOperation.NUMBER_OP_INCREMENT,
"DECREMENT": NumberOperation.NUMBER_OP_DECREMENT,
"TO_MIN": NumberOperation.NUMBER_OP_TO_MIN,
"TO_MAX": NumberOperation.NUMBER_OP_TO_MAX,
}
icon = cv.icon icon = cv.icon
NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
@ -159,12 +171,18 @@ async def to_code(config):
cg.add_global(number_ns.using) cg.add_global(number_ns.using)
OPERATION_BASE_SCHEMA = cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(Number),
}
)
@automation.register_action( @automation.register_action(
"number.set", "number.set",
NumberSetAction, NumberSetAction,
cv.Schema( OPERATION_BASE_SCHEMA.extend(
{ {
cv.Required(CONF_ID): cv.use_id(Number),
cv.Required(CONF_VALUE): cv.templatable(cv.float_), cv.Required(CONF_VALUE): cv.templatable(cv.float_),
} }
), ),
@ -175,3 +193,85 @@ async def number_set_to_code(config, action_id, template_arg, args):
template_ = await cg.templatable(config[CONF_VALUE], args, float) template_ = await cg.templatable(config[CONF_VALUE], args, float)
cg.add(var.set_value(template_)) cg.add(var.set_value(template_))
return var return var
@automation.register_action(
"number.increment",
NumberOperationAction,
automation.maybe_simple_id(
OPERATION_BASE_SCHEMA.extend(
{
cv.Optional(CONF_MODE, default="INCREMENT"): cv.one_of(
"INCREMENT", upper=True
),
cv.Optional(CONF_CYCLE, default=True): cv.boolean,
}
)
),
)
@automation.register_action(
"number.decrement",
NumberOperationAction,
automation.maybe_simple_id(
OPERATION_BASE_SCHEMA.extend(
{
cv.Optional(CONF_MODE, default="DECREMENT"): cv.one_of(
"DECREMENT", upper=True
),
cv.Optional(CONF_CYCLE, default=True): cv.boolean,
}
)
),
)
@automation.register_action(
"number.to_min",
NumberOperationAction,
automation.maybe_simple_id(
OPERATION_BASE_SCHEMA.extend(
{
cv.Optional(CONF_MODE, default="TO_MIN"): cv.one_of(
"TO_MIN", upper=True
),
}
)
),
)
@automation.register_action(
"number.to_max",
NumberOperationAction,
automation.maybe_simple_id(
OPERATION_BASE_SCHEMA.extend(
{
cv.Optional(CONF_MODE, default="TO_MAX"): cv.one_of(
"TO_MAX", upper=True
),
}
)
),
)
@automation.register_action(
"number.operation",
NumberOperationAction,
OPERATION_BASE_SCHEMA.extend(
{
cv.Required(CONF_OPERATION): cv.templatable(
cv.enum(NUMBER_OPERATION_OPTIONS, upper=True)
),
cv.Optional(CONF_CYCLE, default=True): cv.templatable(cv.boolean),
}
),
)
async def number_to_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:
to_ = await cg.templatable(config[CONF_OPERATION], args, NumberOperation)
cg.add(var.set_operation(to_))
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(NUMBER_OPERATION_OPTIONS[config[CONF_MODE]]))
if CONF_CYCLE in config:
cg.add(var.set_cycle(config[CONF_CYCLE]))
return var

View file

@ -29,6 +29,25 @@ template<typename... Ts> class NumberSetAction : public Action<Ts...> {
Number *number_; Number *number_;
}; };
template<typename... Ts> class NumberOperationAction : public Action<Ts...> {
public:
explicit NumberOperationAction(Number *number) : number_(number) {}
TEMPLATABLE_VALUE(NumberOperation, operation)
TEMPLATABLE_VALUE(bool, cycle)
void play(Ts... x) override {
auto call = this->number_->make_call();
call.with_operation(this->operation_.value(x...));
if (this->cycle_.has_value()) {
call.with_cycle(this->cycle_.value(x...));
}
call.perform();
}
protected:
Number *number_;
};
class ValueRangeTrigger : public Trigger<float>, public Component { class ValueRangeTrigger : public Trigger<float>, public Component {
public: public:
explicit ValueRangeTrigger(Number *parent) : parent_(parent) {} explicit ValueRangeTrigger(Number *parent) : parent_(parent) {}

View file

@ -6,30 +6,6 @@ namespace number {
static const char *const TAG = "number"; static const char *const TAG = "number";
void NumberCall::perform() {
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
if (!this->value_.has_value() || std::isnan(*this->value_)) {
ESP_LOGW(TAG, "No value set for NumberCall");
return;
}
const auto &traits = this->parent_->traits;
auto value = *this->value_;
float min_value = traits.get_min_value();
if (value < min_value) {
ESP_LOGW(TAG, " Value %f must not be less than minimum %f", value, min_value);
return;
}
float max_value = traits.get_max_value();
if (value > max_value) {
ESP_LOGW(TAG, " Value %f must not be greater than maximum %f", value, max_value);
return;
}
ESP_LOGD(TAG, " Value: %f", *this->value_);
this->parent_->control(*this->value_);
}
void Number::publish_state(float state) { void Number::publish_state(float state) {
this->has_state_ = true; this->has_state_ = true;
this->state = state; this->state = state;
@ -41,15 +17,6 @@ void Number::add_on_state_callback(std::function<void(float)> &&callback) {
this->state_callback_.add(std::move(callback)); this->state_callback_.add(std::move(callback));
} }
std::string NumberTraits::get_unit_of_measurement() {
if (this->unit_of_measurement_.has_value())
return *this->unit_of_measurement_;
return "";
}
void NumberTraits::set_unit_of_measurement(const std::string &unit_of_measurement) {
this->unit_of_measurement_ = unit_of_measurement;
}
uint32_t Number::hash_base() { return 2282307003UL; } uint32_t Number::hash_base() { return 2282307003UL; }
} // namespace number } // namespace number

View file

@ -3,6 +3,8 @@
#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 "number_call.h"
#include "number_traits.h"
namespace esphome { namespace esphome {
namespace number { namespace number {
@ -20,54 +22,6 @@ namespace number {
class Number; class Number;
class NumberCall {
public:
explicit NumberCall(Number *parent) : parent_(parent) {}
void perform();
NumberCall &set_value(float value) {
value_ = value;
return *this;
}
const optional<float> &get_value() const { return value_; }
protected:
Number *const parent_;
optional<float> value_;
};
enum NumberMode : uint8_t {
NUMBER_MODE_AUTO = 0,
NUMBER_MODE_BOX = 1,
NUMBER_MODE_SLIDER = 2,
};
class NumberTraits {
public:
void set_min_value(float min_value) { min_value_ = min_value; }
float get_min_value() const { return min_value_; }
void set_max_value(float max_value) { max_value_ = max_value; }
float get_max_value() const { return max_value_; }
void set_step(float step) { step_ = step; }
float get_step() const { return step_; }
/// Get the unit of measurement, using the manual override if set.
std::string get_unit_of_measurement();
/// Manually set the unit of measurement.
void set_unit_of_measurement(const std::string &unit_of_measurement);
// Get/set the frontend mode.
NumberMode get_mode() const { return this->mode_; }
void set_mode(NumberMode mode) { this->mode_ = mode; }
protected:
float min_value_ = NAN;
float max_value_ = NAN;
float step_ = NAN;
optional<std::string> unit_of_measurement_; ///< Unit of measurement override
NumberMode mode_{NUMBER_MODE_AUTO};
};
/** Base-class for all numbers. /** Base-class for all numbers.
* *
* A number can use publish_state to send out a new value. * A number can use publish_state to send out a new value.

View file

@ -0,0 +1,118 @@
#include "number_call.h"
#include "number.h"
#include "esphome/core/log.h"
namespace esphome {
namespace number {
static const char *const TAG = "number";
NumberCall &NumberCall::set_value(float value) { return this->with_operation(NUMBER_OP_SET).with_value(value); }
NumberCall &NumberCall::number_increment(bool cycle) {
return this->with_operation(NUMBER_OP_INCREMENT).with_cycle(cycle);
}
NumberCall &NumberCall::number_decrement(bool cycle) {
return this->with_operation(NUMBER_OP_DECREMENT).with_cycle(cycle);
}
NumberCall &NumberCall::number_to_min() { return this->with_operation(NUMBER_OP_TO_MIN); }
NumberCall &NumberCall::number_to_max() { return this->with_operation(NUMBER_OP_TO_MAX); }
NumberCall &NumberCall::with_operation(NumberOperation operation) {
this->operation_ = operation;
return *this;
}
NumberCall &NumberCall::with_value(float value) {
this->value_ = value;
return *this;
}
NumberCall &NumberCall::with_cycle(bool cycle) {
this->cycle_ = cycle;
return *this;
}
void NumberCall::perform() {
auto *parent = this->parent_;
const auto *name = parent->get_name().c_str();
const auto &traits = parent->traits;
if (this->operation_ == NUMBER_OP_NONE) {
ESP_LOGW(TAG, "'%s' - NumberCall performed without selecting an operation", name);
return;
}
float target_value = NAN;
float min_value = traits.get_min_value();
float max_value = traits.get_max_value();
if (this->operation_ == NUMBER_OP_SET) {
ESP_LOGD(TAG, "'%s' - Setting number value", name);
if (!this->value_.has_value() || std::isnan(*this->value_)) {
ESP_LOGW(TAG, "'%s' - No value set for NumberCall", name);
return;
}
target_value = this->value_.value();
} else if (this->operation_ == NUMBER_OP_TO_MIN) {
if (std::isnan(min_value)) {
ESP_LOGW(TAG, "'%s' - Can't set to min value through NumberCall: no min_value defined", name);
} else {
target_value = min_value;
}
} else if (this->operation_ == NUMBER_OP_TO_MAX) {
if (std::isnan(max_value)) {
ESP_LOGW(TAG, "'%s' - Can't set to max value through NumberCall: no max_value defined", name);
} else {
target_value = max_value;
}
} else if (this->operation_ == NUMBER_OP_INCREMENT) {
ESP_LOGD(TAG, "'%s' - Increment number, with%s cycling", name, this->cycle_ ? "" : "out");
if (!parent->has_state()) {
ESP_LOGW(TAG, "'%s' - Can't increment number through NumberCall: no active state to modify", name);
return;
}
auto step = traits.get_step();
target_value = parent->state + (std::isnan(step) ? 1 : step);
if (target_value > max_value) {
if (this->cycle_ && !std::isnan(min_value)) {
target_value = min_value;
} else {
target_value = max_value;
}
}
} else if (this->operation_ == NUMBER_OP_DECREMENT) {
ESP_LOGD(TAG, "'%s' - Decrement number, with%s cycling", name, this->cycle_ ? "" : "out");
if (!parent->has_state()) {
ESP_LOGW(TAG, "'%s' - Can't decrement number through NumberCall: no active state to modify", name);
return;
}
auto step = traits.get_step();
target_value = parent->state - (std::isnan(step) ? 1 : step);
if (target_value < min_value) {
if (this->cycle_ && !std::isnan(max_value)) {
target_value = max_value;
} else {
target_value = min_value;
}
}
}
if (target_value < min_value) {
ESP_LOGW(TAG, "'%s' - Value %f must not be less than minimum %f", name, target_value, min_value);
return;
}
if (target_value > max_value) {
ESP_LOGW(TAG, "'%s' - Value %f must not be greater than maximum %f", name, target_value, max_value);
return;
}
ESP_LOGD(TAG, " New number value: %f", target_value);
this->parent_->control(target_value);
}
} // namespace number
} // namespace esphome

View file

@ -0,0 +1,45 @@
#pragma once
#include "esphome/core/helpers.h"
#include "number_traits.h"
namespace esphome {
namespace number {
class Number;
enum NumberOperation {
NUMBER_OP_NONE,
NUMBER_OP_SET,
NUMBER_OP_INCREMENT,
NUMBER_OP_DECREMENT,
NUMBER_OP_TO_MIN,
NUMBER_OP_TO_MAX,
};
class NumberCall {
public:
explicit NumberCall(Number *parent) : parent_(parent) {}
void perform();
NumberCall &set_value(float value);
const optional<float> &get_value() const { return value_; }
NumberCall &number_increment(bool cycle);
NumberCall &number_decrement(bool cycle);
NumberCall &number_to_min();
NumberCall &number_to_max();
NumberCall &with_operation(NumberOperation operation);
NumberCall &with_value(float value);
NumberCall &with_cycle(bool cycle);
protected:
Number *const parent_;
NumberOperation operation_{NUMBER_OP_NONE};
optional<float> value_;
bool cycle_;
};
} // namespace number
} // namespace esphome

View file

@ -0,0 +1,20 @@
#include "esphome/core/log.h"
#include "number_traits.h"
namespace esphome {
namespace number {
static const char *const TAG = "number";
void NumberTraits::set_unit_of_measurement(const std::string &unit_of_measurement) {
this->unit_of_measurement_ = unit_of_measurement;
}
std::string NumberTraits::get_unit_of_measurement() {
if (this->unit_of_measurement_.has_value())
return *this->unit_of_measurement_;
return "";
}
} // namespace number
} // namespace esphome

View file

@ -0,0 +1,44 @@
#pragma once
#include "esphome/core/helpers.h"
namespace esphome {
namespace number {
enum NumberMode : uint8_t {
NUMBER_MODE_AUTO = 0,
NUMBER_MODE_BOX = 1,
NUMBER_MODE_SLIDER = 2,
};
class NumberTraits {
public:
// Set/get the number value boundaries.
void set_min_value(float min_value) { min_value_ = min_value; }
float get_min_value() const { return min_value_; }
void set_max_value(float max_value) { max_value_ = max_value; }
float get_max_value() const { return max_value_; }
// Set/get the step size for incrementing or decrementing the number value.
void set_step(float step) { step_ = step; }
float get_step() const { return step_; }
/// Manually set the unit of measurement.
void set_unit_of_measurement(const std::string &unit_of_measurement);
/// Get the unit of measurement, using the manual override if set.
std::string get_unit_of_measurement();
// Set/get the frontend mode.
void set_mode(NumberMode mode) { this->mode_ = mode; }
NumberMode get_mode() const { return this->mode_; }
protected:
float min_value_ = NAN;
float max_value_ = NAN;
float step_ = NAN;
optional<std::string> unit_of_measurement_; ///< Unit of measurement override
NumberMode mode_{NUMBER_MODE_AUTO};
};
} // namespace number
} // namespace esphome

View file

@ -120,6 +120,11 @@ number:
name: My template number name: My template number
id: template_number_id id: template_number_id
optimistic: true optimistic: true
max_value: 100
min_value: 0
step: 5
unit_of_measurement: "%"
mode: slider
on_value: on_value:
- logger.log: - logger.log:
format: "Number changed to %f" format: "Number changed to %f"
@ -128,11 +133,31 @@ number:
- logger.log: - logger.log:
format: "Template Number set to %f" format: "Template Number set to %f"
args: ["x"] args: ["x"]
max_value: 100 - number.set:
min_value: 0 id: template_number_id
step: 5 value: 50
unit_of_measurement: "%" - number.to_min: template_number_id
mode: slider - number.to_min:
id: template_number_id
- number.to_max: template_number_id
- number.to_max:
id: template_number_id
- number.increment: template_number_id
- number.increment:
id: template_number_id
cycle: false
- number.decrement: template_number_id
- number.decrement:
id: template_number_id
cycle: false
- number.operation:
id: template_number_id
operation: Increment
cycle: false
- number.operation:
id: template_number_id
operation: !lambda "return NUMBER_OP_INCREMENT;"
cycle: !lambda "return false;"
- id: modbus_numbertest - id: modbus_numbertest
platform: modbus_controller platform: modbus_controller