mirror of
https://github.com/esphome/esphome.git
synced 2026-10-22 13:56:10 +00:00
Compare commits
46
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64c18b8006 | ||
|
|
731134e540 | ||
|
|
7dbec6abf7 | ||
|
|
204a7cd1dd | ||
|
|
754a960ed1 | ||
|
|
40c34b8628 | ||
|
|
f7a60f5859 | ||
|
|
1dc9bb81f2 | ||
|
|
f4ea1d3b21 | ||
|
|
ded8f7f31e | ||
|
|
b8a0de91db | ||
|
|
2c659096de | ||
|
|
a8572e6885 | ||
|
|
3a2f9a24c2 | ||
|
|
fb54defeda | ||
|
|
fbfe2a5590 | ||
|
|
e2d37f0d7e | ||
|
|
2937d81216 | ||
|
|
afa42646fe | ||
|
|
3db32a284c | ||
|
|
9dc43f5668 | ||
|
|
9d59415766 | ||
|
|
298ca0c2b6 | ||
|
|
29ef01a207 | ||
|
|
05a4b256db | ||
|
|
93c1ef5c50 | ||
|
|
bed58f5895 | ||
|
|
7070d516ca | ||
|
|
e8e98ee3db | ||
|
|
6aa647638c | ||
|
|
d0427c36eb | ||
|
|
d48ed8227f | ||
|
|
e2c2fdfb3d | ||
|
|
7425b8d3b1 | ||
|
|
5f697bb0d6 | ||
|
|
8f79467d5f | ||
|
|
5ca927da8e | ||
|
|
776a7f68d8 | ||
|
|
87de7d679e | ||
|
|
e75a10ec7d | ||
|
|
f0ea8edfad | ||
|
|
5df5d44516 | ||
|
|
dd59a827ff | ||
|
|
ed9b566091 | ||
|
|
5fd7c33916 | ||
|
|
a990231342 |
@@ -2,10 +2,11 @@ from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import uart
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_WAKEUP_PIN
|
||||
from esphome.const import CONF_PLATFORM, CONF_WAKEUP_PIN
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .const import CONF_AUTO_WAKE, CONF_WAKEUP_PULSE
|
||||
from .const import CONF_AREA_CONFIG, CONF_AUTO_WAKE, CONF_LD6002B_ID, CONF_WAKEUP_PULSE
|
||||
|
||||
CODEOWNERS = ["@hepter"]
|
||||
DEPENDENCIES = ["uart"]
|
||||
@@ -69,3 +70,33 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add(var.set_wakeup_pulse_ms(config[CONF_WAKEUP_PULSE].total_milliseconds))
|
||||
|
||||
cg.add(var.set_auto_wake(config[CONF_AUTO_WAKE]))
|
||||
|
||||
|
||||
def validate_unique_settings(domain: str, config: ConfigType) -> None:
|
||||
"""Reject a setting set in two platform blocks of one hub, which keeps one entity per setting."""
|
||||
hub_id = config[CONF_LD6002B_ID]
|
||||
entries = [
|
||||
entry
|
||||
for entry in fv.full_config.get().get(domain, [])
|
||||
if entry.get(CONF_PLATFORM) == DOMAIN and entry.get(CONF_LD6002B_ID) == hub_id
|
||||
]
|
||||
for key, value in config.items():
|
||||
if key in (CONF_PLATFORM, CONF_LD6002B_ID):
|
||||
continue
|
||||
# area_config groups six settings, so each one is checked on its own
|
||||
for path in (
|
||||
[[key, sub] for sub in value] if key == CONF_AREA_CONFIG else [[key]]
|
||||
):
|
||||
if sum(_has_path(entry, path) for entry in entries) > 1:
|
||||
raise cv.Invalid(
|
||||
f"{'.'.join(path)} is already set for this ld6002b in another {domain} block",
|
||||
path=path,
|
||||
)
|
||||
|
||||
|
||||
def _has_path(entry: ConfigType, path: list[str]) -> bool:
|
||||
for key in path:
|
||||
if key not in entry:
|
||||
return False
|
||||
entry = entry[key]
|
||||
return True
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from esphome.automation import parent_ref
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import button
|
||||
from esphome.components.select import DOMAIN as SELECT_DOMAIN
|
||||
@@ -9,6 +10,7 @@ from esphome.const import (
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
)
|
||||
from esphome.cpp_generator import ParameterListExpression
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
@@ -33,53 +35,52 @@ from ..const import (
|
||||
|
||||
DEPENDENCIES = ["ld6002b"]
|
||||
|
||||
LD6002BButton = ld6002b_ns.class_("LD6002BButton", button.Button)
|
||||
ButtonType = ld6002b_ns.enum("ButtonType", is_class=True)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_LD6002B_ID): cv.use_id(LD6002BComponent),
|
||||
cv.Optional(CONF_APPLY_AREA): button.button_schema(
|
||||
LD6002BButton, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
button.ApplyButton, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
),
|
||||
cv.Optional(CONF_AUTO_INTERFERENCE): button.button_schema(
|
||||
LD6002BButton, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
button.ApplyButton, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
),
|
||||
cv.Optional(CONF_GET_AREAS): button.button_schema(
|
||||
LD6002BButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
button.ApplyButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
),
|
||||
cv.Optional(CONF_CLEAR_INTERFERENCE): button.button_schema(
|
||||
LD6002BButton, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
button.ApplyButton, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
),
|
||||
cv.Optional(CONF_RESET_DETECTION_AREA): button.button_schema(
|
||||
LD6002BButton, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
button.ApplyButton, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
),
|
||||
cv.Optional(CONF_GET_DELAY): button.button_schema(
|
||||
LD6002BButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
button.ApplyButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
),
|
||||
cv.Optional(CONF_GET_SENSITIVITY): button.button_schema(
|
||||
LD6002BButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
button.ApplyButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
),
|
||||
cv.Optional(CONF_GET_TRIGGER_SPEED): button.button_schema(
|
||||
LD6002BButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
button.ApplyButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
),
|
||||
cv.Optional(CONF_GET_Z_RANGE): button.button_schema(
|
||||
LD6002BButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
button.ApplyButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
),
|
||||
cv.Optional(CONF_GET_INSTALLATION): button.button_schema(
|
||||
LD6002BButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
button.ApplyButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
),
|
||||
cv.Optional(CONF_GET_LOW_POWER_MODE): button.button_schema(
|
||||
LD6002BButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
button.ApplyButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
),
|
||||
cv.Optional(CONF_GET_LOW_POWER_SLEEP_TIME): button.button_schema(
|
||||
LD6002BButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
button.ApplyButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
),
|
||||
cv.Optional(CONF_RESET_UNATTENDED): button.button_schema(
|
||||
LD6002BButton, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
button.ApplyButton, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
),
|
||||
cv.Optional(CONF_WAKE): button.button_schema(
|
||||
LD6002BButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
button.ApplyButton, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -131,8 +132,34 @@ BUTTON_MAP = {
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
for key, button_type in BUTTON_MAP.items():
|
||||
if button_config := config.get(key):
|
||||
b = cg.new_Pvariable(button_config[CONF_ID], button_type)
|
||||
await button.register_button(b, button_config)
|
||||
await cg.register_parented(b, config[CONF_LD6002B_ID])
|
||||
confs = [
|
||||
(button_type, conf)
|
||||
for key, button_type in BUTTON_MAP.items()
|
||||
if (conf := config.get(key)) is not None
|
||||
]
|
||||
if not confs:
|
||||
return
|
||||
hub = await cg.get_variable(config[CONF_LD6002B_ID])
|
||||
params = button.APPLY_ENTITY.params
|
||||
# One function for all buttons, picking the type by pointer: one class instead of one per button.
|
||||
# Named after the first button so two button blocks on one hub do not clash.
|
||||
fn = f"esphome__{confs[0][1][CONF_ID].id}__button_fn"
|
||||
cg.add_global(
|
||||
cg.RawStatement(f"static void {fn}({ParameterListExpression(*params)});")
|
||||
)
|
||||
template = cg.TemplateArguments(cg.RawExpression(fn))
|
||||
created = [
|
||||
(button_type, await button.new_button(conf, template))
|
||||
for button_type, conf in confs
|
||||
]
|
||||
*head, (last_type, _) = created
|
||||
chain = "".join(f"entity == {parent_ref(var)} ? {t} : " for t, var in head)
|
||||
cg.static_function(
|
||||
fn,
|
||||
cg.void,
|
||||
list(params),
|
||||
[
|
||||
f"const auto type = {chain}{last_type};",
|
||||
f"{parent_ref(hub)}->press_button(type);",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "ld6002b_button.h"
|
||||
|
||||
namespace esphome::ld6002b {
|
||||
|
||||
void LD6002BButton::press_action() { this->parent_->press_button(this->type_); }
|
||||
|
||||
} // namespace esphome::ld6002b
|
||||
@@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../ld6002b.h"
|
||||
|
||||
namespace esphome::ld6002b {
|
||||
|
||||
class LD6002BButton : public button::Button, public Parented<LD6002BComponent> {
|
||||
public:
|
||||
explicit LD6002BButton(ButtonType type) : type_(type) {}
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
|
||||
ButtonType type_;
|
||||
};
|
||||
|
||||
} // namespace esphome::ld6002b
|
||||
@@ -1347,92 +1347,74 @@ void LD6002BComponent::wake_() {
|
||||
[this]() { this->wakeup_pin_->digital_write(true); });
|
||||
}
|
||||
|
||||
void LD6002BComponent::set_number_value(NumberType type, float value) {
|
||||
switch (type) {
|
||||
case NumberType::HOLD_DELAY: {
|
||||
uint32_t delay = static_cast<uint32_t>(value);
|
||||
uint8_t data[4];
|
||||
write_u32_le(data, delay);
|
||||
this->queue_command_(TYPE_SET_HOLD_DELAY, data, sizeof(data));
|
||||
break;
|
||||
}
|
||||
case NumberType::Z_MIN:
|
||||
this->z_min_ = value;
|
||||
this->send_z_range_();
|
||||
break;
|
||||
case NumberType::Z_MAX:
|
||||
this->z_max_ = value;
|
||||
this->send_z_range_();
|
||||
break;
|
||||
case NumberType::LOW_POWER_SLEEP: {
|
||||
uint32_t sleep_ms = static_cast<uint32_t>(value);
|
||||
uint8_t data[4];
|
||||
write_u32_le(data, sleep_ms);
|
||||
this->queue_command_(TYPE_SET_LOW_POWER_SLEEP, data, sizeof(data));
|
||||
break;
|
||||
}
|
||||
case NumberType::AREA_X_MIN:
|
||||
this->area_x_min_ = value;
|
||||
this->area_edits_.x_min = value;
|
||||
break;
|
||||
case NumberType::AREA_X_MAX:
|
||||
this->area_x_max_ = value;
|
||||
this->area_edits_.x_max = value;
|
||||
break;
|
||||
case NumberType::AREA_Y_MIN:
|
||||
this->area_y_min_ = value;
|
||||
this->area_edits_.y_min = value;
|
||||
break;
|
||||
case NumberType::AREA_Y_MAX:
|
||||
this->area_y_max_ = value;
|
||||
this->area_edits_.y_max = value;
|
||||
break;
|
||||
case NumberType::AREA_Z_MIN:
|
||||
this->area_z_min_ = value;
|
||||
this->area_edits_.z_min = value;
|
||||
break;
|
||||
case NumberType::AREA_Z_MAX:
|
||||
this->area_z_max_ = value;
|
||||
this->area_edits_.z_max = value;
|
||||
break;
|
||||
#ifdef USE_NUMBER
|
||||
// Each setter picks its setting by entity pointer, so the entities share one generated class.
|
||||
void LD6002BComponent::set_number_value(number::Number *entity, float value) {
|
||||
if (entity == this->hold_delay_number_ || entity == this->low_power_sleep_number_) {
|
||||
uint8_t data[4];
|
||||
write_u32_le(data, static_cast<uint32_t>(value));
|
||||
this->queue_command_(entity == this->hold_delay_number_ ? TYPE_SET_HOLD_DELAY : TYPE_SET_LOW_POWER_SLEEP, data,
|
||||
sizeof(data));
|
||||
} else if (entity == this->z_min_number_) {
|
||||
this->z_min_ = value;
|
||||
this->send_z_range_();
|
||||
} else if (entity == this->z_max_number_) {
|
||||
this->z_max_ = value;
|
||||
this->send_z_range_();
|
||||
} else if (entity == this->area_x_min_number_) {
|
||||
this->area_x_min_ = value;
|
||||
this->area_edits_.x_min = value;
|
||||
} else if (entity == this->area_x_max_number_) {
|
||||
this->area_x_max_ = value;
|
||||
this->area_edits_.x_max = value;
|
||||
} else if (entity == this->area_y_min_number_) {
|
||||
this->area_y_min_ = value;
|
||||
this->area_edits_.y_min = value;
|
||||
} else if (entity == this->area_y_max_number_) {
|
||||
this->area_y_max_ = value;
|
||||
this->area_edits_.y_max = value;
|
||||
} else if (entity == this->area_z_min_number_) {
|
||||
this->area_z_min_ = value;
|
||||
this->area_edits_.z_min = value;
|
||||
} else { // Only registered entities call this, so the last one needs no compare
|
||||
this->area_z_max_ = value;
|
||||
this->area_edits_.z_max = value;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void LD6002BComponent::set_select_value(SelectType type, size_t index) {
|
||||
switch (type) {
|
||||
case SelectType::SENSITIVITY:
|
||||
if (index == 0) {
|
||||
this->send_control_command_(CMD_SENSITIVITY_LOW);
|
||||
} else if (index == 1) {
|
||||
this->send_control_command_(CMD_SENSITIVITY_MEDIUM);
|
||||
} else if (index == 2) {
|
||||
this->send_control_command_(CMD_SENSITIVITY_HIGH);
|
||||
}
|
||||
break;
|
||||
case SelectType::TRIGGER_SPEED:
|
||||
if (index == 0) {
|
||||
this->send_control_command_(CMD_TRIGGER_SLOW);
|
||||
} else if (index == 1) {
|
||||
this->send_control_command_(CMD_TRIGGER_MEDIUM);
|
||||
} else if (index == 2) {
|
||||
this->send_control_command_(CMD_TRIGGER_FAST);
|
||||
}
|
||||
break;
|
||||
case SelectType::INSTALLATION_MODE:
|
||||
if (index == 0) {
|
||||
this->send_control_command_(CMD_INSTALL_TOP);
|
||||
} else if (index == 1) {
|
||||
this->send_control_command_(CMD_INSTALL_SIDE);
|
||||
}
|
||||
break;
|
||||
case SelectType::AREA_ID:
|
||||
this->area_id_ = static_cast<uint8_t>(index);
|
||||
this->area_id_set_ = true;
|
||||
this->update_area_numbers_for_id_(this->area_id_);
|
||||
this->save_area_id_pref_(this->area_id_);
|
||||
break;
|
||||
#ifdef USE_SELECT
|
||||
void LD6002BComponent::set_select_value(select::Select *entity, size_t index) {
|
||||
if (entity == this->sensitivity_select_) {
|
||||
if (index == 0) {
|
||||
this->send_control_command_(CMD_SENSITIVITY_LOW);
|
||||
} else if (index == 1) {
|
||||
this->send_control_command_(CMD_SENSITIVITY_MEDIUM);
|
||||
} else if (index == 2) {
|
||||
this->send_control_command_(CMD_SENSITIVITY_HIGH);
|
||||
}
|
||||
} else if (entity == this->trigger_speed_select_) {
|
||||
if (index == 0) {
|
||||
this->send_control_command_(CMD_TRIGGER_SLOW);
|
||||
} else if (index == 1) {
|
||||
this->send_control_command_(CMD_TRIGGER_MEDIUM);
|
||||
} else if (index == 2) {
|
||||
this->send_control_command_(CMD_TRIGGER_FAST);
|
||||
}
|
||||
} else if (entity == this->installation_select_) {
|
||||
if (index == 0) {
|
||||
this->send_control_command_(CMD_INSTALL_TOP);
|
||||
} else if (index == 1) {
|
||||
this->send_control_command_(CMD_INSTALL_SIDE);
|
||||
}
|
||||
} else { // Only registered entities call this, so the last one needs no compare
|
||||
this->area_id_ = static_cast<uint8_t>(index);
|
||||
this->area_id_set_ = true;
|
||||
this->update_area_numbers_for_id_(this->area_id_);
|
||||
this->save_area_id_pref_(this->area_id_);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void LD6002BComponent::update_area_numbers_(const AreaConfig &area) {
|
||||
// A report refreshes every axis the user is not in the middle of changing. An
|
||||
@@ -1760,37 +1742,35 @@ void LD6002BComponent::clear_target_state_() {
|
||||
}
|
||||
}
|
||||
|
||||
void LD6002BComponent::set_switch_state(SwitchType type, bool state) {
|
||||
switch (type) {
|
||||
case SwitchType::LOW_POWER:
|
||||
this->low_power_enabled_ = state;
|
||||
this->low_power_reported_ = true;
|
||||
this->send_control_command_(state ? CMD_LOW_POWER_ON : CMD_LOW_POWER_OFF);
|
||||
this->update_work_mode_fallback_();
|
||||
break;
|
||||
case SwitchType::POINT_CLOUD:
|
||||
this->point_cloud_enabled_ = state;
|
||||
this->send_control_command_(state ? CMD_POINT_CLOUD_ON : CMD_POINT_CLOUD_OFF);
|
||||
#ifdef USE_SWITCH
|
||||
void LD6002BComponent::set_switch_state(switch_::Switch *entity, bool state) {
|
||||
if (entity == this->low_power_switch_) {
|
||||
this->low_power_enabled_ = state;
|
||||
this->low_power_reported_ = true;
|
||||
this->send_control_command_(state ? CMD_LOW_POWER_ON : CMD_LOW_POWER_OFF);
|
||||
this->update_work_mode_fallback_();
|
||||
} else if (entity == this->point_cloud_switch_) {
|
||||
this->point_cloud_enabled_ = state;
|
||||
this->send_control_command_(state ? CMD_POINT_CLOUD_ON : CMD_POINT_CLOUD_OFF);
|
||||
#ifdef USE_SENSOR
|
||||
// The count only moves while the stream runs, so the last one would stand as
|
||||
// a live reading. The dedup sentinel is cleared with it: the same count is
|
||||
// new again when the stream comes back.
|
||||
if (!state && this->point_count_sensor_ != nullptr && this->last_point_count_ != 0xFFFFFFFF) {
|
||||
this->point_count_sensor_->publish_state(NAN);
|
||||
this->last_point_count_ = 0xFFFFFFFF;
|
||||
}
|
||||
// The count only moves while the stream runs, so the last one would stand as
|
||||
// a live reading. The dedup sentinel is cleared with it: the same count is
|
||||
// new again when the stream comes back.
|
||||
if (!state && this->point_count_sensor_ != nullptr && this->last_point_count_ != 0xFFFFFFFF) {
|
||||
this->point_count_sensor_->publish_state(NAN);
|
||||
this->last_point_count_ = 0xFFFFFFFF;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case SwitchType::TARGET_DISPLAY:
|
||||
this->target_display_enabled_ = state;
|
||||
this->send_control_command_(state ? CMD_TARGET_DISPLAY_ON : CMD_TARGET_DISPLAY_OFF);
|
||||
if (!state) {
|
||||
// Every target entity is fed by the reports this just stopped.
|
||||
this->clear_target_state_();
|
||||
}
|
||||
break;
|
||||
} else { // Only registered entities call this, so the last one needs no compare
|
||||
this->target_display_enabled_ = state;
|
||||
this->send_control_command_(state ? CMD_TARGET_DISPLAY_ON : CMD_TARGET_DISPLAY_OFF);
|
||||
if (!state) {
|
||||
// Every target entity is fed by the reports this just stopped.
|
||||
this->clear_target_state_();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void LD6002BComponent::press_button(ButtonType type) {
|
||||
switch (type) {
|
||||
|
||||
@@ -40,32 +40,6 @@ static constexpr size_t DEFAULT_MAX_DATA_LEN_POINT_CLOUD = 4096;
|
||||
// Largest protocol payload is TYPE_SET_AREA: int32 area id + 6 floats = 28 bytes.
|
||||
static constexpr size_t CMD_MAX_DATA_LEN = 28;
|
||||
|
||||
enum class NumberType : uint8_t {
|
||||
HOLD_DELAY,
|
||||
Z_MIN,
|
||||
Z_MAX,
|
||||
LOW_POWER_SLEEP,
|
||||
AREA_X_MIN,
|
||||
AREA_X_MAX,
|
||||
AREA_Y_MIN,
|
||||
AREA_Y_MAX,
|
||||
AREA_Z_MIN,
|
||||
AREA_Z_MAX,
|
||||
};
|
||||
|
||||
enum class SelectType : uint8_t {
|
||||
SENSITIVITY,
|
||||
TRIGGER_SPEED,
|
||||
INSTALLATION_MODE,
|
||||
AREA_ID,
|
||||
};
|
||||
|
||||
enum class SwitchType : uint8_t {
|
||||
LOW_POWER,
|
||||
POINT_CLOUD,
|
||||
TARGET_DISPLAY,
|
||||
};
|
||||
|
||||
enum class ButtonType : uint8_t {
|
||||
APPLY_AREA,
|
||||
AUTO_INTERFERENCE,
|
||||
@@ -264,9 +238,15 @@ class LD6002BComponent : public Component, public uart::UARTDevice {
|
||||
void set_target_display_switch(switch_::Switch *sw) { this->target_display_switch_ = sw; }
|
||||
#endif
|
||||
|
||||
void set_number_value(NumberType type, float value);
|
||||
void set_select_value(SelectType type, size_t index);
|
||||
void set_switch_state(SwitchType type, bool state);
|
||||
#ifdef USE_NUMBER
|
||||
void set_number_value(number::Number *entity, float value);
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
void set_select_value(select::Select *entity, size_t index);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
void set_switch_state(switch_::Switch *entity, bool state);
|
||||
#endif
|
||||
void press_button(ButtonType type);
|
||||
|
||||
protected:
|
||||
|
||||
@@ -15,7 +15,7 @@ from esphome.const import (
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import LD6002BComponent, ld6002b_ns
|
||||
from .. import LD6002BComponent, validate_unique_settings
|
||||
from ..const import (
|
||||
CONF_APPLY_AREA,
|
||||
CONF_AREA_CONFIG,
|
||||
@@ -32,32 +32,29 @@ from ..const import (
|
||||
|
||||
DEPENDENCIES = ["ld6002b"]
|
||||
|
||||
LD6002BNumber = ld6002b_ns.class_("LD6002BNumber", number.Number)
|
||||
NumberType = ld6002b_ns.enum("NumberType", is_class=True)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_LD6002B_ID): cv.use_id(LD6002BComponent),
|
||||
cv.Optional(CONF_HOLD_DELAY): number.number_schema(
|
||||
LD6002BNumber,
|
||||
number.ApplyNumber,
|
||||
unit_of_measurement=UNIT_SECOND,
|
||||
device_class=DEVICE_CLASS_DURATION,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
),
|
||||
cv.Optional(CONF_Z_MIN): number.number_schema(
|
||||
LD6002BNumber,
|
||||
number.ApplyNumber,
|
||||
unit_of_measurement=UNIT_METER,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
),
|
||||
cv.Optional(CONF_Z_MAX): number.number_schema(
|
||||
LD6002BNumber,
|
||||
number.ApplyNumber,
|
||||
unit_of_measurement=UNIT_METER,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
),
|
||||
cv.Optional(CONF_LOW_POWER_SLEEP_TIME): number.number_schema(
|
||||
LD6002BNumber,
|
||||
number.ApplyNumber,
|
||||
unit_of_measurement=UNIT_MILLISECOND,
|
||||
device_class=DEVICE_CLASS_DURATION,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
@@ -65,37 +62,37 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_AREA_CONFIG): cv.Schema(
|
||||
{
|
||||
cv.Optional(KEY_X_MIN): number.number_schema(
|
||||
LD6002BNumber,
|
||||
number.ApplyNumber,
|
||||
unit_of_measurement=UNIT_METER,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
),
|
||||
cv.Optional(KEY_X_MAX): number.number_schema(
|
||||
LD6002BNumber,
|
||||
number.ApplyNumber,
|
||||
unit_of_measurement=UNIT_METER,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
),
|
||||
cv.Optional(KEY_Y_MIN): number.number_schema(
|
||||
LD6002BNumber,
|
||||
number.ApplyNumber,
|
||||
unit_of_measurement=UNIT_METER,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
),
|
||||
cv.Optional(KEY_Y_MAX): number.number_schema(
|
||||
LD6002BNumber,
|
||||
number.ApplyNumber,
|
||||
unit_of_measurement=UNIT_METER,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
),
|
||||
cv.Optional(CONF_Z_MIN): number.number_schema(
|
||||
LD6002BNumber,
|
||||
number.ApplyNumber,
|
||||
unit_of_measurement=UNIT_METER,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
),
|
||||
cv.Optional(CONF_Z_MAX): number.number_schema(
|
||||
LD6002BNumber,
|
||||
number.ApplyNumber,
|
||||
unit_of_measurement=UNIT_METER,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
@@ -107,6 +104,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
|
||||
|
||||
def final_validate(config: ConfigType) -> None:
|
||||
validate_unique_settings(number.DOMAIN, config)
|
||||
if config.get(CONF_AREA_CONFIG) is None:
|
||||
return
|
||||
|
||||
@@ -139,42 +137,34 @@ FINAL_VALIDATE_SCHEMA = final_validate
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_LD6002B_ID])
|
||||
|
||||
for key, number_type, setter, min_value, max_value, step in (
|
||||
(CONF_HOLD_DELAY, NumberType.HOLD_DELAY, "set_hold_delay_number", 0, 65535, 1),
|
||||
(CONF_Z_MIN, NumberType.Z_MIN, "set_z_min_number", -10, 10, 0.1),
|
||||
(CONF_Z_MAX, NumberType.Z_MAX, "set_z_max_number", -10, 10, 0.1),
|
||||
# 0x0205 carries a uint32 of milliseconds; the vendor documents 500 ms as
|
||||
# the default and no upper bound, so the range ends at a minute rather
|
||||
# than at a default the module is free to be sleeping past.
|
||||
(
|
||||
CONF_LOW_POWER_SLEEP_TIME,
|
||||
NumberType.LOW_POWER_SLEEP,
|
||||
"set_low_power_sleep_number",
|
||||
0,
|
||||
60000,
|
||||
100,
|
||||
),
|
||||
):
|
||||
if conf := config.get(key):
|
||||
n = await number.new_number(
|
||||
conf, number_type, min_value=min_value, max_value=max_value, step=step
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD6002B_ID])
|
||||
cg.add(getattr(hub, setter)(n))
|
||||
control = {"apply": "set_number_value(entity, value)", "publish": "before"}
|
||||
numbers = number.sub_numbers(config, parent=hub)
|
||||
await numbers(
|
||||
CONF_HOLD_DELAY,
|
||||
hub.set_hold_delay_number,
|
||||
min_value=0,
|
||||
max_value=65535,
|
||||
step=1,
|
||||
**control,
|
||||
)
|
||||
meters = {"min_value": -10, "max_value": 10, "step": 0.1, **control}
|
||||
await numbers(CONF_Z_MIN, hub.set_z_min_number, **meters)
|
||||
await numbers(CONF_Z_MAX, hub.set_z_max_number, **meters)
|
||||
# 0x0205 is a uint32 of ms with no documented maximum; cap it at a minute
|
||||
await numbers(
|
||||
CONF_LOW_POWER_SLEEP_TIME,
|
||||
hub.set_low_power_sleep_number,
|
||||
min_value=0,
|
||||
max_value=60000,
|
||||
step=100,
|
||||
**control,
|
||||
)
|
||||
|
||||
if area_config := config.get(CONF_AREA_CONFIG):
|
||||
for key, number_type, setter in (
|
||||
(KEY_X_MIN, NumberType.AREA_X_MIN, "set_area_x_min_number"),
|
||||
(KEY_X_MAX, NumberType.AREA_X_MAX, "set_area_x_max_number"),
|
||||
(KEY_Y_MIN, NumberType.AREA_Y_MIN, "set_area_y_min_number"),
|
||||
(KEY_Y_MAX, NumberType.AREA_Y_MAX, "set_area_y_max_number"),
|
||||
(CONF_Z_MIN, NumberType.AREA_Z_MIN, "set_area_z_min_number"),
|
||||
(CONF_Z_MAX, NumberType.AREA_Z_MAX, "set_area_z_max_number"),
|
||||
):
|
||||
if conf := area_config.get(key):
|
||||
n = await number.new_number(
|
||||
conf, number_type, min_value=-10, max_value=10, step=0.1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD6002B_ID])
|
||||
cg.add(getattr(hub, setter)(n))
|
||||
area = number.sub_numbers(area_config, parent=hub)
|
||||
await area(KEY_X_MIN, hub.set_area_x_min_number, **meters)
|
||||
await area(KEY_X_MAX, hub.set_area_x_max_number, **meters)
|
||||
await area(KEY_Y_MIN, hub.set_area_y_min_number, **meters)
|
||||
await area(KEY_Y_MAX, hub.set_area_y_max_number, **meters)
|
||||
await area(CONF_Z_MIN, hub.set_area_z_min_number, **meters)
|
||||
await area(CONF_Z_MAX, hub.set_area_z_max_number, **meters)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
#include "ld6002b_number.h"
|
||||
|
||||
namespace esphome::ld6002b {
|
||||
|
||||
void LD6002BNumber::control(float value) {
|
||||
this->publish_state(value);
|
||||
this->parent_->set_number_value(this->type_, value);
|
||||
}
|
||||
|
||||
} // namespace esphome::ld6002b
|
||||
@@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/number/number.h"
|
||||
#include "../ld6002b.h"
|
||||
|
||||
namespace esphome::ld6002b {
|
||||
|
||||
class LD6002BNumber : public number::Number, public Parented<LD6002BComponent> {
|
||||
public:
|
||||
explicit LD6002BNumber(NumberType type) : type_(type) {}
|
||||
|
||||
protected:
|
||||
void control(float value) override;
|
||||
|
||||
NumberType type_;
|
||||
};
|
||||
|
||||
} // namespace esphome::ld6002b
|
||||
@@ -1,17 +1,16 @@
|
||||
from functools import partial
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import select
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_AREA_ID, CONF_SENSITIVITY, ENTITY_CATEGORY_CONFIG
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import LD6002BComponent, ld6002b_ns
|
||||
from .. import LD6002BComponent, validate_unique_settings
|
||||
from ..const import CONF_INSTALLATION_MODE, CONF_LD6002B_ID, CONF_TRIGGER_SPEED
|
||||
|
||||
DEPENDENCIES = ["ld6002b"]
|
||||
|
||||
LD6002BSelect = ld6002b_ns.class_("LD6002BSelect", select.Select)
|
||||
SelectType = ld6002b_ns.enum("SelectType", is_class=True)
|
||||
|
||||
AREA_ID_OPTIONS = [
|
||||
"interference_area_0",
|
||||
"interference_area_1",
|
||||
@@ -27,49 +26,46 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_LD6002B_ID): cv.use_id(LD6002BComponent),
|
||||
cv.Optional(CONF_SENSITIVITY): select.select_schema(
|
||||
LD6002BSelect, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
select.ApplySelect, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
),
|
||||
cv.Optional(CONF_TRIGGER_SPEED): select.select_schema(
|
||||
LD6002BSelect, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
select.ApplySelect, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
),
|
||||
cv.Optional(CONF_INSTALLATION_MODE): select.select_schema(
|
||||
LD6002BSelect, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
select.ApplySelect, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
),
|
||||
cv.Optional(CONF_AREA_ID): select.select_schema(
|
||||
LD6002BSelect, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
select.ApplySelect, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
SELECT_MAP = (
|
||||
(
|
||||
CONF_SENSITIVITY,
|
||||
SelectType.SENSITIVITY,
|
||||
"set_sensitivity_select",
|
||||
["low", "medium", "high"],
|
||||
),
|
||||
(
|
||||
CONF_TRIGGER_SPEED,
|
||||
SelectType.TRIGGER_SPEED,
|
||||
"set_trigger_speed_select",
|
||||
["slow", "medium", "fast"],
|
||||
),
|
||||
(
|
||||
CONF_INSTALLATION_MODE,
|
||||
SelectType.INSTALLATION_MODE,
|
||||
"set_installation_select",
|
||||
["top", "side"],
|
||||
),
|
||||
(CONF_AREA_ID, SelectType.AREA_ID, "set_area_id_select", AREA_ID_OPTIONS),
|
||||
)
|
||||
FINAL_VALIDATE_SCHEMA = partial(validate_unique_settings, select.DOMAIN)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_LD6002B_ID])
|
||||
|
||||
for key, select_type, setter, options in SELECT_MAP:
|
||||
if conf := config.get(key):
|
||||
s = await select.new_select(conf, select_type, options=options)
|
||||
await cg.register_parented(s, config[CONF_LD6002B_ID])
|
||||
cg.add(getattr(hub, setter)(s))
|
||||
control = {"apply": "set_select_value(entity, index)", "publish": "before"}
|
||||
selects = select.sub_selects(config, parent=hub)
|
||||
await selects(
|
||||
CONF_SENSITIVITY,
|
||||
hub.set_sensitivity_select,
|
||||
options=["low", "medium", "high"],
|
||||
**control,
|
||||
)
|
||||
await selects(
|
||||
CONF_TRIGGER_SPEED,
|
||||
hub.set_trigger_speed_select,
|
||||
options=["slow", "medium", "fast"],
|
||||
**control,
|
||||
)
|
||||
await selects(
|
||||
CONF_INSTALLATION_MODE,
|
||||
hub.set_installation_select,
|
||||
options=["top", "side"],
|
||||
**control,
|
||||
)
|
||||
await selects(
|
||||
CONF_AREA_ID, hub.set_area_id_select, options=AREA_ID_OPTIONS, **control
|
||||
)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
#include "ld6002b_select.h"
|
||||
|
||||
namespace esphome::ld6002b {
|
||||
|
||||
void LD6002BSelect::control(size_t index) {
|
||||
this->publish_state(index);
|
||||
this->parent_->set_select_value(this->type_, index);
|
||||
}
|
||||
|
||||
} // namespace esphome::ld6002b
|
||||
@@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/select/select.h"
|
||||
#include "../ld6002b.h"
|
||||
|
||||
namespace esphome::ld6002b {
|
||||
|
||||
class LD6002BSelect : public select::Select, public Parented<LD6002BComponent> {
|
||||
public:
|
||||
explicit LD6002BSelect(SelectType type) : type_(type) {}
|
||||
|
||||
protected:
|
||||
void control(size_t index) override;
|
||||
|
||||
SelectType type_;
|
||||
};
|
||||
|
||||
} // namespace esphome::ld6002b
|
||||
@@ -1,10 +1,12 @@
|
||||
from functools import partial
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import switch
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import DEVICE_CLASS_SWITCH, ENTITY_CATEGORY_CONFIG
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import LD6002BComponent, ld6002b_ns
|
||||
from .. import LD6002BComponent, validate_unique_settings
|
||||
from ..const import (
|
||||
CONF_LD6002B_ID,
|
||||
CONF_LOW_POWER,
|
||||
@@ -14,9 +16,6 @@ from ..const import (
|
||||
|
||||
DEPENDENCIES = ["ld6002b"]
|
||||
|
||||
LD6002BSwitch = ld6002b_ns.class_("LD6002BSwitch", switch.Switch)
|
||||
SwitchType = ld6002b_ns.enum("SwitchType", is_class=True)
|
||||
|
||||
# None of these three carry an inversion. They name what the module is doing, not
|
||||
# how something is wired to it, so an inverted one would only report the opposite
|
||||
# of the truth -- and the boot restore, which applies a state nothing reports back,
|
||||
@@ -25,19 +24,19 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_LD6002B_ID): cv.use_id(LD6002BComponent),
|
||||
cv.Optional(CONF_LOW_POWER): switch.switch_schema(
|
||||
LD6002BSwitch,
|
||||
switch.ApplySwitch,
|
||||
block_inverted=True,
|
||||
device_class=DEVICE_CLASS_SWITCH,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
),
|
||||
cv.Optional(CONF_POINT_CLOUD): switch.switch_schema(
|
||||
LD6002BSwitch,
|
||||
switch.ApplySwitch,
|
||||
block_inverted=True,
|
||||
device_class=DEVICE_CLASS_SWITCH,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
),
|
||||
cv.Optional(CONF_TARGET_DISPLAY): switch.switch_schema(
|
||||
LD6002BSwitch,
|
||||
switch.ApplySwitch,
|
||||
block_inverted=True,
|
||||
device_class=DEVICE_CLASS_SWITCH,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
@@ -47,15 +46,13 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = partial(validate_unique_settings, switch.DOMAIN)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_LD6002B_ID])
|
||||
|
||||
for key, switch_type, setter in (
|
||||
(CONF_LOW_POWER, SwitchType.LOW_POWER, "set_low_power_switch"),
|
||||
(CONF_POINT_CLOUD, SwitchType.POINT_CLOUD, "set_point_cloud_switch"),
|
||||
(CONF_TARGET_DISPLAY, SwitchType.TARGET_DISPLAY, "set_target_display_switch"),
|
||||
):
|
||||
if conf := config.get(key):
|
||||
s = await switch.new_switch(conf, switch_type)
|
||||
await cg.register_parented(s, config[CONF_LD6002B_ID])
|
||||
cg.add(getattr(hub, setter)(s))
|
||||
control = {"apply": "set_switch_state(entity, state)", "publish": "after"}
|
||||
switches = switch.sub_switches(config, parent=hub)
|
||||
await switches(CONF_LOW_POWER, hub.set_low_power_switch, **control)
|
||||
await switches(CONF_POINT_CLOUD, hub.set_point_cloud_switch, **control)
|
||||
await switches(CONF_TARGET_DISPLAY, hub.set_target_display_switch, **control)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
#include "ld6002b_switch.h"
|
||||
|
||||
namespace esphome::ld6002b {
|
||||
|
||||
void LD6002BSwitch::write_state(bool state) {
|
||||
this->parent_->set_switch_state(this->type_, state);
|
||||
this->publish_state(state);
|
||||
}
|
||||
|
||||
} // namespace esphome::ld6002b
|
||||
@@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/switch/switch.h"
|
||||
#include "../ld6002b.h"
|
||||
|
||||
namespace esphome::ld6002b {
|
||||
|
||||
class LD6002BSwitch : public switch_::Switch, public Parented<LD6002BComponent> {
|
||||
public:
|
||||
explicit LD6002BSwitch(SwitchType type) : type_(type) {}
|
||||
|
||||
protected:
|
||||
void write_state(bool state) override;
|
||||
|
||||
SwitchType type_;
|
||||
};
|
||||
|
||||
} // namespace esphome::ld6002b
|
||||
@@ -0,0 +1,49 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
uart:
|
||||
- id: uart_a
|
||||
tx_pin: GPIO17
|
||||
rx_pin: GPIO16
|
||||
baud_rate: 115200
|
||||
- id: uart_b
|
||||
tx_pin: GPIO18
|
||||
rx_pin: GPIO19
|
||||
baud_rate: 115200
|
||||
|
||||
ld6002b:
|
||||
- id: hub_a
|
||||
uart_id: uart_a
|
||||
wakeup_pin: GPIO14
|
||||
- id: hub_b
|
||||
uart_id: uart_b
|
||||
|
||||
button:
|
||||
# Two blocks on hub_a: the dispatcher names must not clash.
|
||||
- platform: ld6002b
|
||||
ld6002b_id: hub_a
|
||||
get_delay:
|
||||
id: a_get_delay
|
||||
name: A Get Delay
|
||||
get_sensitivity:
|
||||
id: a_get_sensitivity
|
||||
name: A Get Sensitivity
|
||||
wake:
|
||||
id: a_wake
|
||||
name: A Wake
|
||||
- platform: ld6002b
|
||||
ld6002b_id: hub_a
|
||||
get_z_range:
|
||||
id: a_get_z_range
|
||||
name: A Get Z Range
|
||||
# A single button on hub_b: no pointer chain, only the fallback type.
|
||||
- platform: ld6002b
|
||||
ld6002b_id: hub_b
|
||||
get_installation:
|
||||
id: b_get_installation
|
||||
name: B Get Installation
|
||||
@@ -0,0 +1,82 @@
|
||||
"""Code generation tests for the ld6002b button dispatcher.
|
||||
|
||||
Each button block gets one function that maps the pressed entity to its button
|
||||
type and calls its own hub; the last button of a block is the fallback type.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
YAML = Path(__file__).parent / "button_dispatch.yaml"
|
||||
|
||||
|
||||
def _fn_body(main_cpp: str, fn: str) -> str:
|
||||
match = re.search(
|
||||
rf"static void {fn}\(button::Button \* entity\) \{{\n(.*?)\n\}}",
|
||||
main_cpp,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert match is not None, f"{fn} not defined"
|
||||
return match.group(1)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("fn", "type_expr", "hub"),
|
||||
[
|
||||
# Two blocks on hub_a: each named after its first button, chain ends in the fallback.
|
||||
(
|
||||
"esphome__a_get_delay__button_fn",
|
||||
(
|
||||
"entity == ::a_get_delay ? ld6002b::ButtonType::GET_DELAY : "
|
||||
"entity == ::a_get_sensitivity ? ld6002b::ButtonType::GET_SENSITIVITY : "
|
||||
"ld6002b::ButtonType::WAKE"
|
||||
),
|
||||
"hub_a",
|
||||
),
|
||||
(
|
||||
"esphome__a_get_z_range__button_fn",
|
||||
"ld6002b::ButtonType::GET_Z_RANGE",
|
||||
"hub_a",
|
||||
),
|
||||
# A single button on a second hub: no chain, its own hub.
|
||||
(
|
||||
"esphome__b_get_installation__button_fn",
|
||||
"ld6002b::ButtonType::GET_INSTALLATION",
|
||||
"hub_b",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_dispatch_function(
|
||||
generate_main: Callable[[str | Path], str], fn: str, type_expr: str, hub: str
|
||||
) -> None:
|
||||
main_cpp = generate_main(YAML)
|
||||
|
||||
assert main_cpp.count(f"static void {fn}(button::Button * entity) {{") == 1
|
||||
assert _fn_body(main_cpp, fn) == (
|
||||
f" const auto type = {type_expr};\n ::{hub}->press_button(type);"
|
||||
)
|
||||
|
||||
|
||||
def test_each_button_uses_its_block_function(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
) -> None:
|
||||
main_cpp = generate_main(YAML)
|
||||
|
||||
expected = {
|
||||
"a_get_delay": "esphome__a_get_delay__button_fn",
|
||||
"a_get_sensitivity": "esphome__a_get_delay__button_fn",
|
||||
"a_wake": "esphome__a_get_delay__button_fn",
|
||||
"a_get_z_range": "esphome__a_get_z_range__button_fn",
|
||||
"b_get_installation": "esphome__b_get_installation__button_fn",
|
||||
}
|
||||
for button_id, fn in expected.items():
|
||||
assert f"new({button_id}) button::ApplyButton<{fn}>();" in main_cpp
|
||||
# One definition per block, no more.
|
||||
assert (
|
||||
len(re.findall(r"static void esphome__\w+__button_fn\(.*\) \{", main_cpp)) == 3
|
||||
)
|
||||
@@ -9,11 +9,17 @@ from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components.ld6002b import validate_unique_settings
|
||||
from esphome.components.ld6002b.button import (
|
||||
CONFIG_SCHEMA as BUTTON_CONFIG_SCHEMA,
|
||||
FINAL_VALIDATE_SCHEMA as BUTTON_FINAL_VALIDATE_SCHEMA,
|
||||
)
|
||||
from esphome.components.ld6002b.const import CONF_AREA_CONFIG, CONF_Z_MIN
|
||||
from esphome.components.ld6002b.const import (
|
||||
CONF_AREA_CONFIG,
|
||||
CONF_HOLD_DELAY,
|
||||
CONF_Z_MAX,
|
||||
CONF_Z_MIN,
|
||||
)
|
||||
from esphome.components.ld6002b.number import (
|
||||
CONFIG_SCHEMA as NUMBER_CONFIG_SCHEMA,
|
||||
FINAL_VALIDATE_SCHEMA as NUMBER_FINAL_VALIDATE_SCHEMA,
|
||||
@@ -40,6 +46,7 @@ def _full_config(
|
||||
*,
|
||||
selects: list[ConfigType] | None = None,
|
||||
buttons: list[ConfigType] | None = None,
|
||||
numbers: list[ConfigType] | None = None,
|
||||
) -> Config:
|
||||
"""A full config carrying one ld6002b hub, as the ID pass leaves it.
|
||||
|
||||
@@ -57,6 +64,8 @@ def _full_config(
|
||||
full["select"] = selects
|
||||
if buttons is not None:
|
||||
full[CONF_BUTTON] = buttons
|
||||
if numbers is not None:
|
||||
full["number"] = numbers
|
||||
return full
|
||||
|
||||
|
||||
@@ -214,3 +223,53 @@ def test_area_config_without_select_is_rejected(
|
||||
),
|
||||
):
|
||||
_validated_numbers(_area_numbers())
|
||||
|
||||
|
||||
def _number_block(**settings: ConfigType) -> ConfigType:
|
||||
"""A number platform block on the shared hub, as final validation sees it."""
|
||||
return {
|
||||
"platform": "ld6002b",
|
||||
"ld6002b_id": ID(HUB_ID, is_declaration=False, type="ld6002b"),
|
||||
**settings,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("first", "second", "error"),
|
||||
[
|
||||
(
|
||||
{CONF_HOLD_DELAY: {"name": "A"}},
|
||||
{CONF_HOLD_DELAY: {"name": "B"}},
|
||||
r"^hold_delay is already set .* @ data\['hold_delay'\]$",
|
||||
),
|
||||
(
|
||||
{CONF_AREA_CONFIG: {CONF_Z_MIN: {"name": "A"}}},
|
||||
{CONF_AREA_CONFIG: {CONF_Z_MIN: {"name": "B"}}},
|
||||
r"^area_config\.z_min is already set .* @ data\['area_config'\]\['z_min'\]$",
|
||||
),
|
||||
(
|
||||
{CONF_AREA_CONFIG: {CONF_Z_MIN: {"name": "A"}}},
|
||||
{CONF_AREA_CONFIG: {CONF_Z_MAX: {"name": "B"}}},
|
||||
None,
|
||||
),
|
||||
({CONF_HOLD_DELAY: {"name": "A"}}, {CONF_Z_MIN: {"name": "B"}}, None),
|
||||
],
|
||||
)
|
||||
def test_setting_in_two_blocks_of_one_hub(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
first: ConfigType,
|
||||
second: ConfigType,
|
||||
error: str | None,
|
||||
) -> None:
|
||||
"""Each setting maps to one entity pointer, so two blocks cannot share one."""
|
||||
blocks = [_number_block(**first), _number_block(**second)]
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
full_config=_full_config(_hub(wakeup_pin=False), numbers=blocks),
|
||||
)
|
||||
|
||||
if error is None:
|
||||
validate_unique_settings("number", blocks[1])
|
||||
return
|
||||
with pytest.raises(cv.Invalid, match=error):
|
||||
validate_unique_settings("number", blocks[1])
|
||||
|
||||
Reference in New Issue
Block a user