mirror of
https://github.com/esphome/esphome.git
synced 2025-07-29 14:46:40 +00:00
commit
16292a9f13
@ -1,6 +1,14 @@
|
||||
---
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
|
||||
ci:
|
||||
autoupdate_commit_msg: 'pre-commit: autoupdate'
|
||||
autoupdate_schedule: weekly
|
||||
autofix_prs: false
|
||||
# Skip hooks that have issues in pre-commit CI environment
|
||||
skip: [pylint, yamllint]
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
|
2
Doxyfile
2
Doxyfile
@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 2025.7.0b2
|
||||
PROJECT_NUMBER = 2025.7.0b3
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
@ -20,14 +20,16 @@ adjusted_ids = set()
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.ensure_list(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(EspLdo),
|
||||
cv.Required(CONF_VOLTAGE): cv.All(
|
||||
cv.voltage, cv.float_range(min=0.5, max=2.7)
|
||||
),
|
||||
cv.Required(CONF_CHANNEL): cv.one_of(*CHANNELS, int=True),
|
||||
cv.Optional(CONF_ADJUSTABLE, default=False): cv.boolean,
|
||||
}
|
||||
cv.COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(EspLdo),
|
||||
cv.Required(CONF_VOLTAGE): cv.All(
|
||||
cv.voltage, cv.float_range(min=0.5, max=2.7)
|
||||
),
|
||||
cv.Required(CONF_CHANNEL): cv.one_of(*CHANNELS, int=True),
|
||||
cv.Optional(CONF_ADJUSTABLE, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
),
|
||||
cv.only_with_esp_idf,
|
||||
only_on_variant(supported=[VARIANT_ESP32P4]),
|
||||
|
@ -17,6 +17,9 @@ class EspLdo : public Component {
|
||||
void set_adjustable(bool adjustable) { this->adjustable_ = adjustable; }
|
||||
void set_voltage(float voltage) { this->voltage_ = voltage; }
|
||||
void adjust_voltage(float voltage);
|
||||
float get_setup_priority() const override {
|
||||
return setup_priority::BUS; // LDO setup should be done early
|
||||
}
|
||||
|
||||
protected:
|
||||
int channel_;
|
||||
|
@ -1,11 +1,16 @@
|
||||
import logging
|
||||
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_PIN
|
||||
from esphome.const import CONF_ID, CONF_NAME, CONF_NUMBER, CONF_PIN
|
||||
from esphome.core import CORE
|
||||
|
||||
from .. import gpio_ns
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
GPIOBinarySensor = gpio_ns.class_(
|
||||
"GPIOBinarySensor", binary_sensor.BinarySensor, cg.Component
|
||||
)
|
||||
@ -41,6 +46,22 @@ async def to_code(config):
|
||||
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
||||
cg.add(var.set_pin(pin))
|
||||
|
||||
cg.add(var.set_use_interrupt(config[CONF_USE_INTERRUPT]))
|
||||
if config[CONF_USE_INTERRUPT]:
|
||||
# Check for ESP8266 GPIO16 interrupt limitation
|
||||
# GPIO16 on ESP8266 is a special pin that doesn't support interrupts through
|
||||
# the Arduino attachInterrupt() function. This is the only known GPIO pin
|
||||
# across all supported platforms that has this limitation, so we handle it
|
||||
# here instead of in the platform-specific code.
|
||||
use_interrupt = config[CONF_USE_INTERRUPT]
|
||||
if use_interrupt and CORE.is_esp8266 and config[CONF_PIN][CONF_NUMBER] == 16:
|
||||
_LOGGER.warning(
|
||||
"GPIO binary_sensor '%s': GPIO16 on ESP8266 doesn't support interrupts. "
|
||||
"Falling back to polling mode (same as in ESPHome <2025.7). "
|
||||
"The sensor will work exactly as before, but other pins have better "
|
||||
"performance with interrupts.",
|
||||
config.get(CONF_NAME, config[CONF_ID]),
|
||||
)
|
||||
use_interrupt = False
|
||||
|
||||
cg.add(var.set_use_interrupt(use_interrupt))
|
||||
if use_interrupt:
|
||||
cg.add(var.set_interrupt_type(config[CONF_INTERRUPT_TYPE]))
|
||||
|
@ -180,7 +180,7 @@ async def to_code(config):
|
||||
await speaker.register_speaker(var, config)
|
||||
|
||||
if config[CONF_DAC_TYPE] == "internal":
|
||||
cg.add(var.set_internal_dac_mode(config[CONF_CHANNEL]))
|
||||
cg.add(var.set_internal_dac_mode(config[CONF_MODE]))
|
||||
else:
|
||||
cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN]))
|
||||
if use_legacy():
|
||||
|
@ -5,10 +5,10 @@
|
||||
namespace esphome {
|
||||
namespace ld2420 {
|
||||
|
||||
static const char *const TAG = "LD2420.binary_sensor";
|
||||
static const char *const TAG = "ld2420.binary_sensor";
|
||||
|
||||
void LD2420BinarySensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "LD2420 BinarySensor:");
|
||||
ESP_LOGCONFIG(TAG, "Binary Sensor:");
|
||||
LOG_BINARY_SENSOR(" ", "Presence", this->presence_bsensor_);
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
static const char *const TAG = "LD2420.button";
|
||||
static const char *const TAG = "ld2420.button";
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2420 {
|
||||
|
@ -137,7 +137,7 @@ static const std::string OP_SIMPLE_MODE_STRING = "Simple";
|
||||
// Memory-efficient lookup tables
|
||||
struct StringToUint8 {
|
||||
const char *str;
|
||||
uint8_t value;
|
||||
const uint8_t value;
|
||||
};
|
||||
|
||||
static constexpr StringToUint8 OP_MODE_BY_STR[] = {
|
||||
@ -155,8 +155,9 @@ static constexpr const char *ERR_MESSAGE[] = {
|
||||
// Helper function for lookups
|
||||
template<size_t N> uint8_t find_uint8(const StringToUint8 (&arr)[N], const std::string &str) {
|
||||
for (const auto &entry : arr) {
|
||||
if (str == entry.str)
|
||||
if (str == entry.str) {
|
||||
return entry.value;
|
||||
}
|
||||
}
|
||||
return 0xFF; // Not found
|
||||
}
|
||||
@ -326,15 +327,8 @@ void LD2420Component::revert_config_action() {
|
||||
|
||||
void LD2420Component::loop() {
|
||||
// If there is a active send command do not process it here, the send command call will handle it.
|
||||
if (!this->get_cmd_active_()) {
|
||||
if (!this->available())
|
||||
return;
|
||||
static uint8_t buffer[2048];
|
||||
static uint8_t rx_data;
|
||||
while (this->available()) {
|
||||
rx_data = this->read();
|
||||
this->readline_(rx_data, buffer, sizeof(buffer));
|
||||
}
|
||||
while (!this->cmd_active_ && this->available()) {
|
||||
this->readline_(this->read(), this->buffer_data_, MAX_LINE_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
@ -365,8 +359,9 @@ void LD2420Component::auto_calibrate_sensitivity() {
|
||||
|
||||
// Store average and peak values
|
||||
this->gate_avg[gate] = sum / CALIBRATE_SAMPLES;
|
||||
if (this->gate_peak[gate] < peak)
|
||||
if (this->gate_peak[gate] < peak) {
|
||||
this->gate_peak[gate] = peak;
|
||||
}
|
||||
|
||||
uint32_t calculated_value =
|
||||
(static_cast<uint32_t>(this->gate_peak[gate]) + (move_factor * static_cast<uint32_t>(this->gate_peak[gate])));
|
||||
@ -403,8 +398,9 @@ void LD2420Component::set_operating_mode(const std::string &state) {
|
||||
}
|
||||
} else {
|
||||
// Set the current data back so we don't have new data that can be applied in error.
|
||||
if (this->get_calibration_())
|
||||
if (this->get_calibration_()) {
|
||||
memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
|
||||
}
|
||||
this->set_calibration_(false);
|
||||
}
|
||||
} else {
|
||||
@ -414,30 +410,32 @@ void LD2420Component::set_operating_mode(const std::string &state) {
|
||||
}
|
||||
|
||||
void LD2420Component::readline_(int rx_data, uint8_t *buffer, int len) {
|
||||
static int pos = 0;
|
||||
|
||||
if (rx_data >= 0) {
|
||||
if (pos < len - 1) {
|
||||
buffer[pos++] = rx_data;
|
||||
buffer[pos] = 0;
|
||||
} else {
|
||||
pos = 0;
|
||||
}
|
||||
if (pos >= 4) {
|
||||
if (memcmp(&buffer[pos - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) {
|
||||
this->set_cmd_active_(false); // Set command state to inactive after responce.
|
||||
this->handle_ack_data_(buffer, pos);
|
||||
pos = 0;
|
||||
} else if ((buffer[pos - 2] == 0x0D && buffer[pos - 1] == 0x0A) &&
|
||||
(this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) {
|
||||
this->handle_simple_mode_(buffer, pos);
|
||||
pos = 0;
|
||||
} else if ((memcmp(&buffer[pos - 4], &ENERGY_FRAME_FOOTER, sizeof(ENERGY_FRAME_FOOTER)) == 0) &&
|
||||
(this->get_mode_() == CMD_SYSTEM_MODE_ENERGY)) {
|
||||
this->handle_energy_mode_(buffer, pos);
|
||||
pos = 0;
|
||||
}
|
||||
}
|
||||
if (rx_data < 0) {
|
||||
return; // No data available
|
||||
}
|
||||
if (this->buffer_pos_ < len - 1) {
|
||||
buffer[this->buffer_pos_++] = rx_data;
|
||||
buffer[this->buffer_pos_] = 0;
|
||||
} else {
|
||||
// We should never get here, but just in case...
|
||||
ESP_LOGW(TAG, "Max command length exceeded; ignoring");
|
||||
this->buffer_pos_ = 0;
|
||||
}
|
||||
if (this->buffer_pos_ < 4) {
|
||||
return; // Not enough data to process yet
|
||||
}
|
||||
if (memcmp(&buffer[this->buffer_pos_ - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) {
|
||||
this->cmd_active_ = false; // Set command state to inactive after response
|
||||
this->handle_ack_data_(buffer, this->buffer_pos_);
|
||||
this->buffer_pos_ = 0;
|
||||
} else if ((buffer[this->buffer_pos_ - 2] == 0x0D && buffer[this->buffer_pos_ - 1] == 0x0A) &&
|
||||
(this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) {
|
||||
this->handle_simple_mode_(buffer, this->buffer_pos_);
|
||||
this->buffer_pos_ = 0;
|
||||
} else if ((memcmp(&buffer[this->buffer_pos_ - 4], &ENERGY_FRAME_FOOTER, sizeof(ENERGY_FRAME_FOOTER)) == 0) &&
|
||||
(this->get_mode_() == CMD_SYSTEM_MODE_ENERGY)) {
|
||||
this->handle_energy_mode_(buffer, this->buffer_pos_);
|
||||
this->buffer_pos_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -462,8 +460,9 @@ void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) {
|
||||
|
||||
// Resonable refresh rate for home assistant database size health
|
||||
const int32_t current_millis = App.get_loop_component_start_time();
|
||||
if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS)
|
||||
if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS) {
|
||||
return;
|
||||
}
|
||||
this->last_periodic_millis = current_millis;
|
||||
for (auto &listener : this->listeners_) {
|
||||
listener->on_distance(this->get_distance_());
|
||||
@ -506,14 +505,16 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
|
||||
}
|
||||
}
|
||||
outbuf[index] = '\0';
|
||||
if (index > 1)
|
||||
if (index > 1) {
|
||||
this->set_distance_(strtol(outbuf, &endptr, 10));
|
||||
}
|
||||
|
||||
if (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE) {
|
||||
// Resonable refresh rate for home assistant database size health
|
||||
const int32_t current_millis = App.get_loop_component_start_time();
|
||||
if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS)
|
||||
if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS) {
|
||||
return;
|
||||
}
|
||||
this->last_normal_periodic_millis = current_millis;
|
||||
for (auto &listener : this->listeners_)
|
||||
listener->on_distance(this->get_distance_());
|
||||
@ -593,11 +594,12 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
|
||||
int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
|
||||
uint32_t start_millis = millis();
|
||||
uint8_t error = 0;
|
||||
uint8_t ack_buffer[64];
|
||||
uint8_t cmd_buffer[64];
|
||||
uint8_t ack_buffer[MAX_LINE_LENGTH];
|
||||
uint8_t cmd_buffer[MAX_LINE_LENGTH];
|
||||
this->cmd_reply_.ack = false;
|
||||
if (frame.command != CMD_RESTART)
|
||||
this->set_cmd_active_(true); // Restart does not reply, thus no ack state required.
|
||||
if (frame.command != CMD_RESTART) {
|
||||
this->cmd_active_ = true;
|
||||
} // Restart does not reply, thus no ack state required
|
||||
uint8_t retry = 3;
|
||||
while (retry) {
|
||||
frame.length = 0;
|
||||
@ -619,9 +621,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
|
||||
|
||||
memcpy(cmd_buffer + frame.length, &frame.footer, sizeof(frame.footer));
|
||||
frame.length += sizeof(frame.footer);
|
||||
for (uint16_t index = 0; index < frame.length; index++) {
|
||||
this->write_byte(cmd_buffer[index]);
|
||||
}
|
||||
this->write_array(cmd_buffer, frame.length);
|
||||
|
||||
error = 0;
|
||||
if (frame.command == CMD_RESTART) {
|
||||
@ -630,7 +630,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
|
||||
|
||||
while (!this->cmd_reply_.ack) {
|
||||
while (this->available()) {
|
||||
this->readline_(read(), ack_buffer, sizeof(ack_buffer));
|
||||
this->readline_(this->read(), ack_buffer, sizeof(ack_buffer));
|
||||
}
|
||||
delay_microseconds_safe(1450);
|
||||
// Wait on an Rx from the LD2420 for up to 3 1 second loops, otherwise it could trigger a WDT.
|
||||
@ -641,10 +641,12 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this->cmd_reply_.ack)
|
||||
if (this->cmd_reply_.ack) {
|
||||
retry = 0;
|
||||
if (this->cmd_reply_.error > 0)
|
||||
}
|
||||
if (this->cmd_reply_.error > 0) {
|
||||
this->handle_cmd_error(error);
|
||||
}
|
||||
}
|
||||
return error;
|
||||
}
|
||||
@ -764,8 +766,9 @@ void LD2420Component::set_system_mode(uint16_t mode) {
|
||||
cmd_frame.data_length += sizeof(unknown_parm);
|
||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||
ESP_LOGV(TAG, "Sending write system mode command: %2X", cmd_frame.command);
|
||||
if (this->send_cmd_from_array(cmd_frame) == 0)
|
||||
if (this->send_cmd_from_array(cmd_frame) == 0) {
|
||||
this->set_mode_(mode);
|
||||
}
|
||||
}
|
||||
|
||||
void LD2420Component::get_firmware_version_() {
|
||||
@ -840,18 +843,24 @@ void LD2420Component::set_gate_threshold(uint8_t gate) {
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
void LD2420Component::init_gate_config_numbers() {
|
||||
if (this->gate_timeout_number_ != nullptr)
|
||||
if (this->gate_timeout_number_ != nullptr) {
|
||||
this->gate_timeout_number_->publish_state(static_cast<uint16_t>(this->current_config.timeout));
|
||||
if (this->gate_select_number_ != nullptr)
|
||||
}
|
||||
if (this->gate_select_number_ != nullptr) {
|
||||
this->gate_select_number_->publish_state(0);
|
||||
if (this->min_gate_distance_number_ != nullptr)
|
||||
}
|
||||
if (this->min_gate_distance_number_ != nullptr) {
|
||||
this->min_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.min_gate));
|
||||
if (this->max_gate_distance_number_ != nullptr)
|
||||
}
|
||||
if (this->max_gate_distance_number_ != nullptr) {
|
||||
this->max_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.max_gate));
|
||||
if (this->gate_move_sensitivity_factor_number_ != nullptr)
|
||||
}
|
||||
if (this->gate_move_sensitivity_factor_number_ != nullptr) {
|
||||
this->gate_move_sensitivity_factor_number_->publish_state(this->gate_move_sensitivity_factor);
|
||||
if (this->gate_still_sensitivity_factor_number_ != nullptr)
|
||||
}
|
||||
if (this->gate_still_sensitivity_factor_number_ != nullptr) {
|
||||
this->gate_still_sensitivity_factor_number_->publish_state(this->gate_still_sensitivity_factor);
|
||||
}
|
||||
for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) {
|
||||
if (this->gate_still_threshold_numbers_[gate] != nullptr) {
|
||||
this->gate_still_threshold_numbers_[gate]->publish_state(
|
||||
|
@ -20,8 +20,9 @@
|
||||
namespace esphome {
|
||||
namespace ld2420 {
|
||||
|
||||
static const uint8_t TOTAL_GATES = 16;
|
||||
static const uint8_t CALIBRATE_SAMPLES = 64;
|
||||
static const uint8_t MAX_LINE_LENGTH = 46; // Max characters for serial buffer
|
||||
static const uint8_t TOTAL_GATES = 16;
|
||||
|
||||
enum OpMode : uint8_t {
|
||||
OP_NORMAL_MODE = 1,
|
||||
@ -118,10 +119,10 @@ class LD2420Component : public Component, public uart::UARTDevice {
|
||||
|
||||
float gate_move_sensitivity_factor{0.5};
|
||||
float gate_still_sensitivity_factor{0.5};
|
||||
int32_t last_periodic_millis = millis();
|
||||
int32_t report_periodic_millis = millis();
|
||||
int32_t monitor_periodic_millis = millis();
|
||||
int32_t last_normal_periodic_millis = millis();
|
||||
int32_t last_periodic_millis{0};
|
||||
int32_t report_periodic_millis{0};
|
||||
int32_t monitor_periodic_millis{0};
|
||||
int32_t last_normal_periodic_millis{0};
|
||||
uint16_t radar_data[TOTAL_GATES][CALIBRATE_SAMPLES];
|
||||
uint16_t gate_avg[TOTAL_GATES];
|
||||
uint16_t gate_peak[TOTAL_GATES];
|
||||
@ -161,8 +162,6 @@ class LD2420Component : public Component, public uart::UARTDevice {
|
||||
void set_presence_(bool presence) { this->presence_ = presence; };
|
||||
uint16_t get_distance_() { return this->distance_; };
|
||||
void set_distance_(uint16_t distance) { this->distance_ = distance; };
|
||||
bool get_cmd_active_() { return this->cmd_active_; };
|
||||
void set_cmd_active_(bool active) { this->cmd_active_ = active; };
|
||||
void handle_simple_mode_(const uint8_t *inbuf, int len);
|
||||
void handle_energy_mode_(uint8_t *buffer, int len);
|
||||
void handle_ack_data_(uint8_t *buffer, int len);
|
||||
@ -181,12 +180,11 @@ class LD2420Component : public Component, public uart::UARTDevice {
|
||||
std::vector<number::Number *> gate_move_threshold_numbers_ = std::vector<number::Number *>(16);
|
||||
#endif
|
||||
|
||||
uint32_t max_distance_gate_;
|
||||
uint32_t min_distance_gate_;
|
||||
uint16_t distance_{0};
|
||||
uint16_t system_mode_;
|
||||
uint16_t gate_energy_[TOTAL_GATES];
|
||||
uint16_t distance_{0};
|
||||
uint8_t config_checksum_{0};
|
||||
uint8_t buffer_pos_{0}; // where to resume processing/populating buffer
|
||||
uint8_t buffer_data_[MAX_LINE_LENGTH];
|
||||
char firmware_ver_[8]{"v0.0.0"};
|
||||
bool cmd_active_{false};
|
||||
bool presence_{false};
|
||||
|
@ -2,7 +2,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
static const char *const TAG = "LD2420.number";
|
||||
static const char *const TAG = "ld2420.number";
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2420 {
|
||||
|
@ -5,7 +5,7 @@
|
||||
namespace esphome {
|
||||
namespace ld2420 {
|
||||
|
||||
static const char *const TAG = "LD2420.select";
|
||||
static const char *const TAG = "ld2420.select";
|
||||
|
||||
void LD2420Select::control(const std::string &value) {
|
||||
this->publish_state(value);
|
||||
|
@ -5,10 +5,10 @@
|
||||
namespace esphome {
|
||||
namespace ld2420 {
|
||||
|
||||
static const char *const TAG = "LD2420.sensor";
|
||||
static const char *const TAG = "ld2420.sensor";
|
||||
|
||||
void LD2420Sensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "LD2420 Sensor:");
|
||||
ESP_LOGCONFIG(TAG, "Sensor:");
|
||||
LOG_SENSOR(" ", "Distance", this->distance_sensor_);
|
||||
}
|
||||
|
||||
|
@ -5,10 +5,10 @@
|
||||
namespace esphome {
|
||||
namespace ld2420 {
|
||||
|
||||
static const char *const TAG = "LD2420.text_sensor";
|
||||
static const char *const TAG = "ld2420.text_sensor";
|
||||
|
||||
void LD2420TextSensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "LD2420 TextSensor:");
|
||||
ESP_LOGCONFIG(TAG, "Text Sensor:");
|
||||
LOG_TEXT_SENSOR(" ", "Firmware", this->fw_version_text_sensor_);
|
||||
}
|
||||
|
||||
|
@ -29,9 +29,9 @@ from ..defines import (
|
||||
)
|
||||
from ..helpers import add_lv_use, lvgl_components_required
|
||||
from ..lv_validation import (
|
||||
angle,
|
||||
get_end_value,
|
||||
get_start_value,
|
||||
lv_angle,
|
||||
lv_bool,
|
||||
lv_color,
|
||||
lv_float,
|
||||
@ -162,7 +162,7 @@ SCALE_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_RANGE_FROM, default=0.0): cv.float_,
|
||||
cv.Optional(CONF_RANGE_TO, default=100.0): cv.float_,
|
||||
cv.Optional(CONF_ANGLE_RANGE, default=270): cv.int_range(0, 360),
|
||||
cv.Optional(CONF_ROTATION): angle,
|
||||
cv.Optional(CONF_ROTATION): lv_angle,
|
||||
cv.Optional(CONF_INDICATORS): cv.ensure_list(INDICATOR_SCHEMA),
|
||||
}
|
||||
)
|
||||
@ -187,7 +187,7 @@ class MeterType(WidgetType):
|
||||
for scale_conf in config.get(CONF_SCALES, ()):
|
||||
rotation = 90 + (360 - scale_conf[CONF_ANGLE_RANGE]) / 2
|
||||
if CONF_ROTATION in scale_conf:
|
||||
rotation = scale_conf[CONF_ROTATION] // 10
|
||||
rotation = await lv_angle.process(scale_conf[CONF_ROTATION])
|
||||
with LocalVariable(
|
||||
"meter_var", "lv_meter_scale_t", lv_expr.meter_add_scale(var)
|
||||
) as meter_var:
|
||||
@ -205,21 +205,20 @@ class MeterType(WidgetType):
|
||||
var,
|
||||
meter_var,
|
||||
ticks[CONF_COUNT],
|
||||
ticks[CONF_WIDTH],
|
||||
ticks[CONF_LENGTH],
|
||||
await size.process(ticks[CONF_WIDTH]),
|
||||
await size.process(ticks[CONF_LENGTH]),
|
||||
color,
|
||||
)
|
||||
if CONF_MAJOR in ticks:
|
||||
major = ticks[CONF_MAJOR]
|
||||
color = await lv_color.process(major[CONF_COLOR])
|
||||
lv.meter_set_scale_major_ticks(
|
||||
var,
|
||||
meter_var,
|
||||
major[CONF_STRIDE],
|
||||
major[CONF_WIDTH],
|
||||
major[CONF_LENGTH],
|
||||
color,
|
||||
major[CONF_LABEL_GAP],
|
||||
await size.process(major[CONF_WIDTH]),
|
||||
await size.process(major[CONF_LENGTH]),
|
||||
await lv_color.process(major[CONF_COLOR]),
|
||||
await size.process(major[CONF_LABEL_GAP]),
|
||||
)
|
||||
for indicator in scale_conf.get(CONF_INDICATORS, ()):
|
||||
(t, v) = next(iter(indicator.items()))
|
||||
@ -233,7 +232,11 @@ class MeterType(WidgetType):
|
||||
lv_assign(
|
||||
ivar,
|
||||
lv_expr.meter_add_needle_line(
|
||||
var, meter_var, v[CONF_WIDTH], color, v[CONF_R_MOD]
|
||||
var,
|
||||
meter_var,
|
||||
await size.process(v[CONF_WIDTH]),
|
||||
color,
|
||||
await size.process(v[CONF_R_MOD]),
|
||||
),
|
||||
)
|
||||
if t == CONF_ARC:
|
||||
@ -241,7 +244,11 @@ class MeterType(WidgetType):
|
||||
lv_assign(
|
||||
ivar,
|
||||
lv_expr.meter_add_arc(
|
||||
var, meter_var, v[CONF_WIDTH], color, v[CONF_R_MOD]
|
||||
var,
|
||||
meter_var,
|
||||
await size.process(v[CONF_WIDTH]),
|
||||
color,
|
||||
await size.process(v[CONF_R_MOD]),
|
||||
),
|
||||
)
|
||||
if t == CONF_TICK_STYLE:
|
||||
@ -257,7 +264,7 @@ class MeterType(WidgetType):
|
||||
color_start,
|
||||
color_end,
|
||||
v[CONF_LOCAL],
|
||||
v[CONF_WIDTH],
|
||||
size.process(v[CONF_WIDTH]),
|
||||
),
|
||||
)
|
||||
if t == CONF_IMAGE:
|
||||
|
@ -151,8 +151,11 @@ def _substitute_item(substitutions, item, path, jinja, ignore_missing):
|
||||
if sub is not None:
|
||||
item[k] = sub
|
||||
for old, new in replace_keys:
|
||||
item[new] = merge_config(item.get(old), item.get(new))
|
||||
del item[old]
|
||||
if str(new) == str(old):
|
||||
item[new] = item[old]
|
||||
else:
|
||||
item[new] = merge_config(item.get(old), item.get(new))
|
||||
del item[old]
|
||||
elif isinstance(item, str):
|
||||
sub = _expand_substitutions(substitutions, item, path, jinja, ignore_missing)
|
||||
if isinstance(sub, JinjaStr) or sub != item:
|
||||
|
@ -4,7 +4,7 @@ from enum import Enum
|
||||
|
||||
from esphome.enum import StrEnum
|
||||
|
||||
__version__ = "2025.7.0b2"
|
||||
__version__ = "2025.7.0b3"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
|
@ -138,7 +138,7 @@ void Component::call_dump_config() {
|
||||
}
|
||||
}
|
||||
}
|
||||
ESP_LOGE(TAG, " Component %s is marked FAILED: %s", this->get_component_source(), error_msg);
|
||||
ESP_LOGE(TAG, " %s is marked FAILED: %s", this->get_component_source(), error_msg);
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,7 +191,7 @@ bool Component::should_warn_of_blocking(uint32_t blocking_time) {
|
||||
return false;
|
||||
}
|
||||
void Component::mark_failed() {
|
||||
ESP_LOGE(TAG, "Component %s was marked as failed", this->get_component_source());
|
||||
ESP_LOGE(TAG, "%s was marked as failed", this->get_component_source());
|
||||
this->component_state_ &= ~COMPONENT_STATE_MASK;
|
||||
this->component_state_ |= COMPONENT_STATE_FAILED;
|
||||
this->status_set_error();
|
||||
@ -229,7 +229,7 @@ void IRAM_ATTR HOT Component::enable_loop_soon_any_context() {
|
||||
}
|
||||
void Component::reset_to_construction_state() {
|
||||
if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED) {
|
||||
ESP_LOGI(TAG, "Component %s is being reset to construction state", this->get_component_source());
|
||||
ESP_LOGI(TAG, "%s is being reset to construction state", this->get_component_source());
|
||||
this->component_state_ &= ~COMPONENT_STATE_MASK;
|
||||
this->component_state_ |= COMPONENT_STATE_CONSTRUCTION;
|
||||
// Clear error status when resetting
|
||||
@ -275,14 +275,14 @@ void Component::status_set_warning(const char *message) {
|
||||
return;
|
||||
this->component_state_ |= STATUS_LED_WARNING;
|
||||
App.app_state_ |= STATUS_LED_WARNING;
|
||||
ESP_LOGW(TAG, "Component %s set Warning flag: %s", this->get_component_source(), message);
|
||||
ESP_LOGW(TAG, "%s set Warning flag: %s", this->get_component_source(), message);
|
||||
}
|
||||
void Component::status_set_error(const char *message) {
|
||||
if ((this->component_state_ & STATUS_LED_ERROR) != 0)
|
||||
return;
|
||||
this->component_state_ |= STATUS_LED_ERROR;
|
||||
App.app_state_ |= STATUS_LED_ERROR;
|
||||
ESP_LOGE(TAG, "Component %s set Error flag: %s", this->get_component_source(), message);
|
||||
ESP_LOGE(TAG, "%s set Error flag: %s", this->get_component_source(), message);
|
||||
if (strcmp(message, "unspecified") != 0) {
|
||||
// Lazy allocate the error messages vector if needed
|
||||
if (!component_error_messages) {
|
||||
@ -303,13 +303,13 @@ void Component::status_clear_warning() {
|
||||
if ((this->component_state_ & STATUS_LED_WARNING) == 0)
|
||||
return;
|
||||
this->component_state_ &= ~STATUS_LED_WARNING;
|
||||
ESP_LOGW(TAG, "Component %s cleared Warning flag", this->get_component_source());
|
||||
ESP_LOGW(TAG, "%s cleared Warning flag", this->get_component_source());
|
||||
}
|
||||
void Component::status_clear_error() {
|
||||
if ((this->component_state_ & STATUS_LED_ERROR) == 0)
|
||||
return;
|
||||
this->component_state_ &= ~STATUS_LED_ERROR;
|
||||
ESP_LOGE(TAG, "Component %s cleared Error flag", this->get_component_source());
|
||||
ESP_LOGE(TAG, "%s cleared Error flag", this->get_component_source());
|
||||
}
|
||||
void Component::status_momentary_warning(const std::string &name, uint32_t length) {
|
||||
this->status_set_warning();
|
||||
@ -403,7 +403,7 @@ uint32_t WarnIfComponentBlockingGuard::finish() {
|
||||
}
|
||||
if (should_warn) {
|
||||
const char *src = component_ == nullptr ? "<null>" : component_->get_component_source();
|
||||
ESP_LOGW(TAG, "Component %s took a long time for an operation (%" PRIu32 " ms)", src, blocking_time);
|
||||
ESP_LOGW(TAG, "%s took a long time for an operation (%" PRIu32 " ms)", src, blocking_time);
|
||||
ESP_LOGW(TAG, "Components should block for at most 30 ms");
|
||||
}
|
||||
|
||||
|
@ -783,7 +783,7 @@ template<class T> class RAMAllocator {
|
||||
T *reallocate(T *p, size_t n) { return this->reallocate(p, n, sizeof(T)); }
|
||||
|
||||
T *reallocate(T *p, size_t n, size_t manual_size) {
|
||||
size_t size = n * sizeof(T);
|
||||
size_t size = n * manual_size;
|
||||
T *ptr = nullptr;
|
||||
#ifdef USE_ESP32
|
||||
if (this->flags_ & Flags::ALLOC_EXTERNAL) {
|
||||
|
69
tests/component_tests/gpio/test_gpio_binary_sensor.py
Normal file
69
tests/component_tests/gpio/test_gpio_binary_sensor.py
Normal file
@ -0,0 +1,69 @@
|
||||
"""Tests for the GPIO binary sensor component."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_gpio_binary_sensor_basic_setup(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
) -> None:
|
||||
"""
|
||||
When the GPIO binary sensor is set in the yaml file, it should be registered in main
|
||||
"""
|
||||
main_cpp = generate_main("tests/component_tests/gpio/test_gpio_binary_sensor.yaml")
|
||||
|
||||
assert "new gpio::GPIOBinarySensor();" in main_cpp
|
||||
assert "App.register_binary_sensor" in main_cpp
|
||||
assert "bs_gpio->set_use_interrupt(true);" in main_cpp
|
||||
assert "bs_gpio->set_interrupt_type(gpio::INTERRUPT_ANY_EDGE);" in main_cpp
|
||||
|
||||
|
||||
def test_gpio_binary_sensor_esp8266_gpio16_disables_interrupt(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""
|
||||
Test that ESP8266 GPIO16 automatically disables interrupt mode with a warning
|
||||
"""
|
||||
main_cpp = generate_main(
|
||||
"tests/component_tests/gpio/test_gpio_binary_sensor_esp8266.yaml"
|
||||
)
|
||||
|
||||
# Check that interrupt is disabled for GPIO16
|
||||
assert "bs_gpio16->set_use_interrupt(false);" in main_cpp
|
||||
|
||||
# Check that the warning was logged
|
||||
assert "GPIO16 on ESP8266 doesn't support interrupts" in caplog.text
|
||||
assert "Falling back to polling mode" in caplog.text
|
||||
|
||||
|
||||
def test_gpio_binary_sensor_esp8266_other_pins_use_interrupt(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
) -> None:
|
||||
"""
|
||||
Test that ESP8266 pins other than GPIO16 still use interrupt mode
|
||||
"""
|
||||
main_cpp = generate_main(
|
||||
"tests/component_tests/gpio/test_gpio_binary_sensor_esp8266.yaml"
|
||||
)
|
||||
|
||||
# GPIO5 should still use interrupts
|
||||
assert "bs_gpio5->set_use_interrupt(true);" in main_cpp
|
||||
assert "bs_gpio5->set_interrupt_type(gpio::INTERRUPT_ANY_EDGE);" in main_cpp
|
||||
|
||||
|
||||
def test_gpio_binary_sensor_explicit_polling_mode(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
) -> None:
|
||||
"""
|
||||
Test that explicitly setting use_interrupt: false works
|
||||
"""
|
||||
main_cpp = generate_main(
|
||||
"tests/component_tests/gpio/test_gpio_binary_sensor_polling.yaml"
|
||||
)
|
||||
|
||||
assert "bs_polling->set_use_interrupt(false);" in main_cpp
|
11
tests/component_tests/gpio/test_gpio_binary_sensor.yaml
Normal file
11
tests/component_tests/gpio/test_gpio_binary_sensor.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
pin: 5
|
||||
name: "Test GPIO Binary Sensor"
|
||||
id: bs_gpio
|
@ -0,0 +1,20 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp8266:
|
||||
board: d1_mini
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: 16
|
||||
mode: INPUT_PULLDOWN_16
|
||||
name: "GPIO16 Touch Sensor"
|
||||
id: bs_gpio16
|
||||
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: 5
|
||||
mode: INPUT_PULLUP
|
||||
name: "GPIO5 Button"
|
||||
id: bs_gpio5
|
@ -0,0 +1,12 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
pin: 5
|
||||
name: "Polling Mode Sensor"
|
||||
id: bs_polling
|
||||
use_interrupt: false
|
@ -6,6 +6,7 @@ esp_ldo:
|
||||
- id: ldo_4
|
||||
channel: 4
|
||||
voltage: 2.0V
|
||||
setup_priority: 900
|
||||
|
||||
esphome:
|
||||
on_boot:
|
||||
|
@ -919,21 +919,21 @@ lvgl:
|
||||
text_color: 0xFFFFFF
|
||||
scales:
|
||||
- ticks:
|
||||
width: 1
|
||||
width: !lambda return 1;
|
||||
count: 61
|
||||
length: 20
|
||||
length: 20%
|
||||
color: 0xFFFFFF
|
||||
range_from: 0
|
||||
range_to: 60
|
||||
angle_range: 360
|
||||
rotation: 270
|
||||
rotation: !lambda return 2700;
|
||||
indicators:
|
||||
- line:
|
||||
opa: 50%
|
||||
id: minute_hand
|
||||
color: 0xFF0000
|
||||
r_mod: -1
|
||||
width: 3
|
||||
r_mod: !lambda return -1;
|
||||
width: !lambda return 3;
|
||||
-
|
||||
angle_range: 330
|
||||
rotation: 300
|
||||
|
@ -17,3 +17,5 @@ test_list:
|
||||
- ${undefined_var}
|
||||
- $undefined_var
|
||||
- ${ undefined_var }
|
||||
- key1: 1
|
||||
key2: 2
|
||||
|
@ -19,3 +19,5 @@ test_list:
|
||||
- ${undefined_var}
|
||||
- $undefined_var
|
||||
- ${ undefined_var }
|
||||
- key${var1}: 1
|
||||
key${var2}: 2
|
||||
|
@ -6,6 +6,7 @@ package_result:
|
||||
root file
|
||||
- Double substitution also works; the value of var7 is 79, where A is a package
|
||||
var
|
||||
- key79: Key should substitute to key79
|
||||
local_results:
|
||||
- The value of B is 5
|
||||
- 'You will see, however, that
|
||||
|
@ -1,3 +1,4 @@
|
||||
package_result:
|
||||
- The value of A*B is ${A * B}, where A is a package var and B is a substitution in the root file
|
||||
- Double substitution also works; the value of var7 is ${var$A}, where A is a package var
|
||||
- key${var7}: Key should substitute to key79
|
||||
|
Loading…
x
Reference in New Issue
Block a user