mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 05:24:53 +01:00
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:
parent
44b68f140e
commit
d9caab4108
9 changed files with 380 additions and 88 deletions
|
@ -14,6 +14,8 @@ from esphome.const import (
|
|||
CONF_UNIT_OF_MEASUREMENT,
|
||||
CONF_MQTT_ID,
|
||||
CONF_VALUE,
|
||||
CONF_OPERATION,
|
||||
CONF_CYCLE,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
@ -35,6 +37,7 @@ ValueRangeTrigger = number_ns.class_(
|
|||
|
||||
# Actions
|
||||
NumberSetAction = number_ns.class_("NumberSetAction", automation.Action)
|
||||
NumberOperationAction = number_ns.class_("NumberOperationAction", automation.Action)
|
||||
|
||||
# Conditions
|
||||
NumberInRangeCondition = number_ns.class_(
|
||||
|
@ -49,6 +52,15 @@ NUMBER_MODES = {
|
|||
"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
|
||||
|
||||
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)
|
||||
|
||||
|
||||
OPERATION_BASE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Number),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"number.set",
|
||||
NumberSetAction,
|
||||
cv.Schema(
|
||||
OPERATION_BASE_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Number),
|
||||
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)
|
||||
cg.add(var.set_value(template_))
|
||||
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
|
||||
|
|
|
@ -29,6 +29,25 @@ template<typename... Ts> class NumberSetAction : public Action<Ts...> {
|
|||
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 {
|
||||
public:
|
||||
explicit ValueRangeTrigger(Number *parent) : parent_(parent) {}
|
||||
|
|
|
@ -6,30 +6,6 @@ namespace 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) {
|
||||
this->has_state_ = true;
|
||||
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));
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
} // namespace number
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "number_call.h"
|
||||
#include "number_traits.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace number {
|
||||
|
@ -20,54 +22,6 @@ namespace 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.
|
||||
*
|
||||
* A number can use publish_state to send out a new value.
|
||||
|
|
118
esphome/components/number/number_call.cpp
Normal file
118
esphome/components/number/number_call.cpp
Normal 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
|
45
esphome/components/number/number_call.h
Normal file
45
esphome/components/number/number_call.h
Normal 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
|
20
esphome/components/number/number_traits.cpp
Normal file
20
esphome/components/number/number_traits.cpp
Normal 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
|
44
esphome/components/number/number_traits.h
Normal file
44
esphome/components/number/number_traits.h
Normal 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
|
|
@ -120,6 +120,11 @@ number:
|
|||
name: My template number
|
||||
id: template_number_id
|
||||
optimistic: true
|
||||
max_value: 100
|
||||
min_value: 0
|
||||
step: 5
|
||||
unit_of_measurement: "%"
|
||||
mode: slider
|
||||
on_value:
|
||||
- logger.log:
|
||||
format: "Number changed to %f"
|
||||
|
@ -128,11 +133,31 @@ number:
|
|||
- logger.log:
|
||||
format: "Template Number set to %f"
|
||||
args: ["x"]
|
||||
max_value: 100
|
||||
min_value: 0
|
||||
step: 5
|
||||
unit_of_measurement: "%"
|
||||
mode: slider
|
||||
- number.set:
|
||||
id: template_number_id
|
||||
value: 50
|
||||
- number.to_min: template_number_id
|
||||
- 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
|
||||
platform: modbus_controller
|
||||
|
|
Loading…
Reference in a new issue