mirror of
https://github.com/esphome/esphome.git
synced 2026-10-22 05:46:12 +00:00
[output] Add ApplyFloatOutput and ApplyBinaryOutput
This commit is contained in:
@@ -13,7 +13,9 @@ from esphome.const import (
|
||||
CONF_MIN_POWER,
|
||||
CONF_POWER_SUPPLY,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.core import CORE, ID
|
||||
from esphome.core.entity_helpers import ApplyEntity, SubEntities
|
||||
from esphome.cpp_generator import MockObj, SafeExpType
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
@@ -54,6 +56,8 @@ BinaryOutput = output_ns.class_("BinaryOutput")
|
||||
BinaryOutputPtr = BinaryOutput.operator("ptr")
|
||||
FloatOutput = output_ns.class_("FloatOutput", BinaryOutput)
|
||||
FloatOutputPtr = FloatOutput.operator("ptr")
|
||||
ApplyBinaryOutput = output_ns.class_("ApplyBinaryOutput", BinaryOutput)
|
||||
ApplyFloatOutput = output_ns.class_("ApplyFloatOutput", FloatOutput)
|
||||
|
||||
|
||||
async def setup_output_platform_(obj, config):
|
||||
@@ -84,6 +88,34 @@ async def register_output(var, config):
|
||||
await setup_output_platform_(var, config)
|
||||
|
||||
|
||||
async def new_output(config: ConfigType, *args: SafeExpType) -> MockObj:
|
||||
var = cg.new_Pvariable(config[CONF_ID], *args)
|
||||
await register_output(var, config)
|
||||
return var
|
||||
|
||||
|
||||
BINARY_APPLY_ENTITY = ApplyEntity(
|
||||
ApplyBinaryOutput, ((BinaryOutputPtr, "entity"), (cg.bool_, "state"))
|
||||
)
|
||||
FLOAT_APPLY_ENTITY = ApplyEntity(
|
||||
ApplyFloatOutput, ((FloatOutputPtr, "entity"), (cg.float_, "state"))
|
||||
)
|
||||
|
||||
|
||||
def sub_binary_outputs(
|
||||
config: ConfigType, *, parent: MockObj | ID | None = None
|
||||
) -> SubEntities:
|
||||
"""Return a SubEntities bound to new_output; ``apply`` statements can use ``entity`` and ``state``."""
|
||||
return SubEntities(new_output, config, parent, BINARY_APPLY_ENTITY)
|
||||
|
||||
|
||||
def sub_float_outputs(
|
||||
config: ConfigType, *, parent: MockObj | ID | None = None
|
||||
) -> SubEntities:
|
||||
"""Return a SubEntities bound to new_output; ``apply`` gets the scaled level as ``state``."""
|
||||
return SubEntities(new_output, config, parent, FLOAT_APPLY_ENTITY)
|
||||
|
||||
|
||||
BINARY_OUTPUT_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(BinaryOutput),
|
||||
|
||||
@@ -68,4 +68,14 @@ class BinaryOutput {
|
||||
#endif
|
||||
};
|
||||
|
||||
/// Calls the codegen-generated Fn(this, state) on a state write; the hub is baked into Fn.
|
||||
template<auto Fn> class ApplyBinaryOutput final : public BinaryOutput {
|
||||
public:
|
||||
// Not "= default": placement new would zero-fill .bss that is already zero.
|
||||
ApplyBinaryOutput() {}
|
||||
|
||||
protected:
|
||||
void write_state(bool state) override { Fn(this, state); }
|
||||
};
|
||||
|
||||
} // namespace esphome::output
|
||||
|
||||
@@ -130,4 +130,14 @@ class FloatOutput : public BinaryOutput {
|
||||
#endif
|
||||
};
|
||||
|
||||
/// Calls the codegen-generated Fn(this, state) with the scaled, inverted level; the hub is baked into Fn.
|
||||
template<auto Fn> class ApplyFloatOutput final : public FloatOutput {
|
||||
public:
|
||||
// Not "= default": placement new would zero-fill .bss that is already zero.
|
||||
ApplyFloatOutput() {}
|
||||
|
||||
protected:
|
||||
void write_state(float state) override { Fn(this, state); }
|
||||
};
|
||||
|
||||
} // namespace esphome::output
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/components/output/binary_output.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
|
||||
namespace esphome::output::testing {
|
||||
|
||||
static void *last_output = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static std::vector<bool> bool_writes; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static std::vector<float> levels; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
static void on_binary_write(BinaryOutput *output, bool state) {
|
||||
last_output = output;
|
||||
bool_writes.push_back(state);
|
||||
}
|
||||
|
||||
static void on_float_write(FloatOutput *output, float state) {
|
||||
last_output = output;
|
||||
levels.push_back(state);
|
||||
}
|
||||
|
||||
TEST(ApplyBinaryOutput, WriteRunsTheFunctionWithTheState) {
|
||||
last_output = nullptr;
|
||||
bool_writes.clear();
|
||||
ApplyBinaryOutput<on_binary_write> out;
|
||||
out.turn_on();
|
||||
out.turn_off();
|
||||
EXPECT_EQ(bool_writes, (std::vector<bool>{true, false}));
|
||||
EXPECT_EQ(last_output, &out);
|
||||
}
|
||||
|
||||
TEST(ApplyBinaryOutput, InversionIsAppliedBeforeTheFunction) {
|
||||
bool_writes.clear();
|
||||
ApplyBinaryOutput<on_binary_write> out;
|
||||
out.set_inverted(true);
|
||||
out.turn_on();
|
||||
EXPECT_EQ(bool_writes, (std::vector<bool>{false}));
|
||||
}
|
||||
|
||||
TEST(ApplyFloatOutput, WriteRunsTheFunctionWithTheLevel) {
|
||||
last_output = nullptr;
|
||||
levels.clear();
|
||||
ApplyFloatOutput<on_float_write> out;
|
||||
out.set_level(0.25f);
|
||||
out.turn_on();
|
||||
out.turn_off();
|
||||
EXPECT_EQ(levels, (std::vector<float>{0.25f, 1.0f, 0.0f}));
|
||||
EXPECT_EQ(last_output, &out);
|
||||
}
|
||||
|
||||
TEST(ApplyFloatOutput, InversionAndClampAreAppliedBeforeTheFunction) {
|
||||
levels.clear();
|
||||
ApplyFloatOutput<on_float_write> out;
|
||||
out.set_inverted(true);
|
||||
out.set_level(0.25f);
|
||||
out.set_level(2.0f);
|
||||
EXPECT_EQ(levels, (std::vector<float>{0.75f, 0.0f}));
|
||||
}
|
||||
|
||||
TEST(ApplyOutput, HoldsNoMoreThanTheBase) {
|
||||
EXPECT_EQ(sizeof(ApplyBinaryOutput<on_binary_write>), sizeof(BinaryOutput));
|
||||
EXPECT_EQ(sizeof(ApplyFloatOutput<on_float_write>), sizeof(FloatOutput));
|
||||
}
|
||||
|
||||
} // namespace esphome::output::testing
|
||||
@@ -8,7 +8,7 @@ from typing import Any
|
||||
import pytest
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import button, number, select, switch
|
||||
from esphome.components import button, number, output, select, switch
|
||||
from esphome.config_validation import Invalid
|
||||
from esphome.const import (
|
||||
CONF_DEVICE_CLASS,
|
||||
@@ -1565,6 +1565,18 @@ async def test_sub_entities_apply_needs_parent(
|
||||
"select::Select * entity, size_t index",
|
||||
"entity->publish_state(index);",
|
||||
),
|
||||
(
|
||||
output.sub_binary_outputs,
|
||||
output.ApplyBinaryOutput,
|
||||
"output::BinaryOutput * entity, bool state",
|
||||
None,
|
||||
),
|
||||
(
|
||||
output.sub_float_outputs,
|
||||
output.ApplyFloatOutput,
|
||||
"output::FloatOutput * entity, float state",
|
||||
None,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_domain_apply_entities(
|
||||
|
||||
Reference in New Issue
Block a user