[dfrobot_sen0395] Forward trigger arguments in settings actions (#20885)

Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Igor Stadnyk
2026-10-16 10:03:07 -10:00
committed by GitHub
co-authored by J. Nick Koston J. Nick Koston
parent 57dd09018e
commit c0b7c5e590
3 changed files with 171 additions and 6 deletions
@@ -26,17 +26,22 @@ class DfrobotSen0395SettingsAction final : public Action<Ts...>, public Parented
TEMPLATABLE_VALUE(float, det_min4)
TEMPLATABLE_VALUE(float, det_max4)
void play(const Ts &...x) {
void play(const Ts &...x) override {
this->parent_->enqueue(make_unique<PowerCommand>(0));
if (this->factory_reset_.has_value() && this->factory_reset_.value(x...) == true) {
this->parent_->enqueue(make_unique<FactoryResetCommand>());
}
if (this->det_min1_.has_value() && this->det_max1_.has_value()) {
if (this->det_min1_.value() >= 0 && this->det_max1_.value() >= 0) {
this->parent_->enqueue(make_unique<DetRangeCfgCommand>(
this->det_min1_.value_or(-1), this->det_max1_.value_or(-1), this->det_min2_.value_or(-1),
this->det_max2_.value_or(-1), this->det_min3_.value_or(-1), this->det_max3_.value_or(-1),
this->det_min4_.value_or(-1), this->det_max4_.value_or(-1)));
// Each segment is read once, so the guard and the command see the same values.
const float min1 = this->det_min1_.value(x...);
if (min1 >= 0) {
const float max1 = this->det_max1_.value(x...);
if (max1 >= 0) {
this->parent_->enqueue(make_unique<DetRangeCfgCommand>(
min1, max1, this->det_min2_.value_or(x..., -1), this->det_max2_.value_or(x..., -1),
this->det_min3_.value_or(x..., -1), this->det_max3_.value_or(x..., -1),
this->det_min4_.value_or(x..., -1), this->det_max4_.value_or(x..., -1)));
}
}
}
if (this->delay_after_detect_.has_value() && this->delay_after_disappear_.has_value()) {
@@ -17,6 +17,21 @@ esphome:
dfrobot_sen0395:
- id: mmwave
number:
- platform: template
id: mmwave_max_distance
name: mmwave max distance
min_value: 1
max_value: 9
step: 0.5
set_action:
- dfrobot_sen0395.settings:
id: mmwave
detection_segments:
- 0cm
- !lambda "return x;"
sensitivity: 5
binary_sensor:
- platform: dfrobot_sen0395
id: mmwave_detected
@@ -0,0 +1,145 @@
#include <array>
#include <gtest/gtest.h>
#include "esphome/components/dfrobot_sen0395/automation.h"
namespace esphome::dfrobot_sen0395::testing {
namespace {
struct CallRecord {
std::array<int, 8> segment_calls{};
std::array<float, 8> seen_float{};
std::array<int, 8> seen_int{};
int sensitivity_calls{0};
float sensitivity_argument{0};
};
// TemplatableFn stores only a function pointer, so the test lambdas cannot capture. They record into this
// function-local static instead, which SetUp() clears before every test.
CallRecord &call_record() {
static CallRecord record{};
return record;
}
float segment_value(size_t index, float x, int y = 0) {
CallRecord &record = call_record();
record.segment_calls[index]++;
record.seen_float[index] = x;
record.seen_int[index] = y;
return x + y + static_cast<float>(index);
}
} // namespace
// The component is final and its command queue protected, so these tests check the segment
// reads and their arguments; the enqueued DetRangeCfgCommand itself is not inspected.
class SettingsActionTest : public ::testing::Test {
protected:
void SetUp() override { call_record() = CallRecord{}; }
DfrobotSen0395Component radar_;
};
TEST_F(SettingsActionTest, EmptyTriggerPreservesSegmentCallableCounts) {
DfrobotSen0395SettingsAction<> action;
action.set_parent(&this->radar_);
action.set_det_min1([]() -> float { return segment_value(0, 0); });
action.set_det_max1([]() -> float { return segment_value(1, 0); });
action.set_det_min2([]() -> float { return segment_value(2, 0); });
action.set_det_max2([]() -> float { return segment_value(3, 0); });
action.set_det_min3([]() -> float { return segment_value(4, 0); });
action.set_det_max3([]() -> float { return segment_value(5, 0); });
action.set_det_min4([]() -> float { return segment_value(6, 0); });
action.set_det_max4([]() -> float { return segment_value(7, 0); });
action.play();
for (size_t i = 0; i < call_record().segment_calls.size(); i++) {
EXPECT_EQ(call_record().segment_calls[i], 1) << i;
}
}
TEST_F(SettingsActionTest, EmptyTriggerWithoutSegmentsIsValid) {
DfrobotSen0395SettingsAction<> action;
action.set_parent(&this->radar_);
action.play();
for (auto calls : call_record().segment_calls) {
EXPECT_EQ(calls, 0);
}
}
TEST_F(SettingsActionTest, EmptyTriggerNegativeMinKeepsShortCircuit) {
DfrobotSen0395SettingsAction<> action;
action.set_parent(&this->radar_);
action.set_det_min1([]() -> float { return segment_value(0, -1); });
action.set_det_max1([]() -> float { return segment_value(1, 0); });
action.play();
EXPECT_EQ(call_record().segment_calls[0], 1);
EXPECT_EQ(call_record().segment_calls[1], 0);
}
TEST_F(SettingsActionTest, FloatTriggerForwardsAllSegmentsAndPreservesCounts) {
DfrobotSen0395SettingsAction<float> action;
action.set_parent(&this->radar_);
action.set_det_min1([](float x) -> float { return segment_value(0, x); });
action.set_det_max1([](float x) -> float { return segment_value(1, x); });
action.set_det_min2([](float x) -> float { return segment_value(2, x); });
action.set_det_max2([](float x) -> float { return segment_value(3, x); });
action.set_det_min3([](float x) -> float { return segment_value(4, x); });
action.set_det_max3([](float x) -> float { return segment_value(5, x); });
action.set_det_min4([](float x) -> float { return segment_value(6, x); });
action.set_det_max4([](float x) -> float { return segment_value(7, x); });
action.play(2.5f);
for (size_t i = 0; i < call_record().segment_calls.size(); i++) {
EXPECT_EQ(call_record().segment_calls[i], 1) << i;
EXPECT_FLOAT_EQ(call_record().seen_float[i], 2.5f) << i;
}
}
TEST_F(SettingsActionTest, MultipleTriggerArgumentsReachConfiguredSegments) {
DfrobotSen0395SettingsAction<float, int> action;
action.set_parent(&this->radar_);
action.set_det_min1([](float x, int y) -> float { return segment_value(0, x, y); });
action.set_det_max1([](float x, int y) -> float { return segment_value(1, x, y); });
action.play(1.5f, 2);
for (size_t i = 0; i < 2; i++) {
EXPECT_EQ(call_record().segment_calls[i], 1) << i;
EXPECT_FLOAT_EQ(call_record().seen_float[i], 1.5f) << i;
EXPECT_EQ(call_record().seen_int[i], 2) << i;
}
for (size_t i = 2; i < call_record().segment_calls.size(); i++) {
EXPECT_EQ(call_record().segment_calls[i], 0) << i;
}
}
TEST_F(SettingsActionTest, FloatTriggerWithoutSegmentsStillEvaluatesSensitivity) {
DfrobotSen0395SettingsAction<float> action;
action.set_parent(&this->radar_);
action.set_sensitivity([](float x) -> int8_t {
call_record().sensitivity_calls++;
call_record().sensitivity_argument = x;
return 5;
});
action.play(3.25f);
EXPECT_EQ(call_record().sensitivity_calls, 1);
EXPECT_FLOAT_EQ(call_record().sensitivity_argument, 3.25f);
for (auto calls : call_record().segment_calls) {
EXPECT_EQ(calls, 0);
}
}
TEST_F(SettingsActionTest, FloatTriggerNegativeMinKeepsShortCircuit) {
DfrobotSen0395SettingsAction<float> action;
action.set_parent(&this->radar_);
action.set_det_min1([](float x) -> float { return segment_value(0, x); });
action.set_det_max1([](float x) -> float { return segment_value(1, x); });
action.play(-1.0f);
EXPECT_EQ(call_record().segment_calls[0], 1);
EXPECT_EQ(call_record().segment_calls[1], 0);
EXPECT_FLOAT_EQ(call_record().seen_float[0], -1.0f);
}
TEST_F(SettingsActionTest, FloatTriggerUnpairedSegmentIsNotEvaluated) {
DfrobotSen0395SettingsAction<float> action;
action.set_parent(&this->radar_);
action.set_det_min1([](float x) -> float { return segment_value(0, x); });
action.play(4.0f);
EXPECT_EQ(call_record().segment_calls[0], 0);
}
} // namespace esphome::dfrobot_sen0395::testing