Compare commits
20 Commits
applicatio
...
ld2410_arr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
357ef51beb | ||
|
|
2fc8131164 | ||
|
|
26007175be | ||
|
|
09e5aa6011 | ||
|
|
9549304007 | ||
|
|
f7ac32ceda | ||
|
|
92365f133d | ||
|
|
461e10ca3e | ||
|
|
205dc9c6f8 | ||
|
|
9daa9a6de8 | ||
|
|
f5eca9ce8d | ||
|
|
487dd6c8d1 | ||
|
|
23b1e428de | ||
|
|
67a25e56b0 | ||
|
|
a27f7cd765 | ||
|
|
f029f4f20e | ||
|
|
79e3d2b2d7 | ||
|
|
c74e5e0f04 | ||
|
|
15ef93ccc9 | ||
|
|
e017250445 |
23
.github/workflows/lock.yml
vendored
23
.github/workflows/lock.yml
vendored
@@ -1,28 +1,11 @@
|
||||
---
|
||||
name: Lock
|
||||
name: Lock closed issues and PRs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 0 * * *"
|
||||
- cron: "30 0 * * *" # Run daily at 00:30 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: lock
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v5.0.1
|
||||
with:
|
||||
pr-inactive-days: "1"
|
||||
pr-lock-reason: ""
|
||||
exclude-any-pr-labels: keep-open
|
||||
|
||||
issue-inactive-days: "7"
|
||||
issue-lock-reason: ""
|
||||
exclude-any-issue-labels: keep-open
|
||||
uses: esphome/workflows/.github/workflows/lock.yml@main
|
||||
|
||||
@@ -65,6 +65,10 @@ uint32_t APIConnection::get_batch_delay_ms_() const { return this->parent_->get_
|
||||
void APIConnection::start() {
|
||||
this->last_traffic_ = App.get_loop_component_start_time();
|
||||
|
||||
// Set next_ping_retry_ to prevent immediate ping
|
||||
// This ensures the first ping happens after the keepalive period
|
||||
this->next_ping_retry_ = this->last_traffic_ + KEEPALIVE_TIMEOUT_MS;
|
||||
|
||||
APIError err = this->helper_->init();
|
||||
if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
@@ -159,15 +163,20 @@ void APIConnection::loop() {
|
||||
on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->get_client_combined_info().c_str());
|
||||
}
|
||||
} else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS) {
|
||||
} else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && now > this->next_ping_retry_) {
|
||||
ESP_LOGVV(TAG, "Sending keepalive PING");
|
||||
this->sent_ping_ = this->send_message(PingRequest());
|
||||
if (!this->sent_ping_) {
|
||||
// If we can't send the ping request directly (tx_buffer full),
|
||||
// schedule it at the front of the batch so it will be sent with priority
|
||||
ESP_LOGVV(TAG, "Failed to send ping directly, scheduling at front of batch");
|
||||
this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE);
|
||||
this->sent_ping_ = true; // Mark as sent to avoid scheduling multiple pings
|
||||
this->next_ping_retry_ = now + PING_RETRY_INTERVAL;
|
||||
this->ping_retries_++;
|
||||
if (this->ping_retries_ >= MAX_PING_RETRIES) {
|
||||
on_fatal_error();
|
||||
ESP_LOGE(TAG, "%s: Ping failed %u times", this->get_client_combined_info().c_str(), this->ping_retries_);
|
||||
} else if (this->ping_retries_ >= 10) {
|
||||
ESP_LOGW(TAG, "%s: Ping retry %u", this->get_client_combined_info().c_str(), this->ping_retries_);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "%s: Ping retry %u", this->get_client_combined_info().c_str(), this->ping_retries_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,6 +276,11 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t mes
|
||||
// Encode directly into buffer
|
||||
msg.encode(buffer);
|
||||
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
// Log the message for VV debugging
|
||||
conn->log_send_message_(msg.message_name(), msg.dump());
|
||||
#endif
|
||||
|
||||
// Calculate actual encoded size (not including header that was already added)
|
||||
size_t actual_payload_size = shared_buf.size() - size_before_encode;
|
||||
|
||||
@@ -1743,11 +1757,6 @@ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator c
|
||||
items.emplace_back(entity, std::move(creator), message_type);
|
||||
}
|
||||
|
||||
void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type) {
|
||||
// Insert at front for high priority messages (no deduplication check)
|
||||
items.insert(items.begin(), BatchItem(entity, std::move(creator), message_type));
|
||||
}
|
||||
|
||||
bool APIConnection::schedule_batch_() {
|
||||
if (!this->deferred_batch_.batch_scheduled) {
|
||||
this->deferred_batch_.batch_scheduled = true;
|
||||
@@ -1929,12 +1938,6 @@ uint16_t APIConnection::try_send_disconnect_request(EntityBase *entity, APIConne
|
||||
return encode_message_to_buffer(req, DisconnectRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
|
||||
uint16_t APIConnection::try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) {
|
||||
PingRequest req;
|
||||
return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
|
||||
uint16_t APIConnection::get_estimated_message_size(uint16_t message_type) {
|
||||
// Use generated ESTIMATED_SIZE constants from each message type
|
||||
switch (message_type) {
|
||||
|
||||
@@ -185,6 +185,7 @@ class APIConnection : public APIServerConnection {
|
||||
void on_disconnect_response(const DisconnectResponse &value) override;
|
||||
void on_ping_response(const PingResponse &value) override {
|
||||
// we initiated ping
|
||||
this->ping_retries_ = 0;
|
||||
this->sent_ping_ = false;
|
||||
}
|
||||
void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override;
|
||||
@@ -440,16 +441,13 @@ class APIConnection : public APIServerConnection {
|
||||
// Helper function to get estimated message size for buffer pre-allocation
|
||||
static uint16_t get_estimated_message_size(uint16_t message_type);
|
||||
|
||||
// Batch message method for ping requests
|
||||
static uint16_t try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single);
|
||||
|
||||
// Pointers first (4 bytes each, naturally aligned)
|
||||
std::unique_ptr<APIFrameHelper> helper_;
|
||||
APIServer *parent_;
|
||||
|
||||
// 4-byte aligned types
|
||||
uint32_t last_traffic_;
|
||||
uint32_t next_ping_retry_{0};
|
||||
int state_subs_at_ = -1;
|
||||
|
||||
// Strings (12 bytes each on 32-bit)
|
||||
@@ -472,7 +470,8 @@ class APIConnection : public APIServerConnection {
|
||||
bool sent_ping_{false};
|
||||
bool service_call_subscription_{false};
|
||||
bool next_close_ = false;
|
||||
// 7 bytes used, 1 byte padding
|
||||
uint8_t ping_retries_{0};
|
||||
// 8 bytes used, no padding needed
|
||||
|
||||
// Larger objects at the end
|
||||
InitialStateIterator initial_state_iterator_;
|
||||
@@ -599,8 +598,6 @@ class APIConnection : public APIServerConnection {
|
||||
|
||||
// Add item to the batch
|
||||
void add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type);
|
||||
// Add item to the front of the batch (for high priority messages like ping)
|
||||
void add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type);
|
||||
void clear() {
|
||||
items.clear();
|
||||
batch_scheduled = false;
|
||||
@@ -640,12 +637,6 @@ class APIConnection : public APIServerConnection {
|
||||
bool schedule_message_(EntityBase *entity, MessageCreatorPtr function_ptr, uint16_t message_type) {
|
||||
return schedule_message_(entity, MessageCreator(function_ptr), message_type);
|
||||
}
|
||||
|
||||
// Helper function to schedule a high priority message at the front of the batch
|
||||
bool schedule_message_front_(EntityBase *entity, MessageCreatorPtr function_ptr, uint16_t message_type) {
|
||||
this->deferred_batch_.add_item_front(entity, MessageCreator(function_ptr), message_type);
|
||||
return this->schedule_batch_();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,7 @@ class APIServerConnectionBase : public ProtoService {
|
||||
|
||||
template<typename T> bool send_message(const T &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
this->log_send_message_(T::message_name(), msg.dump());
|
||||
this->log_send_message_(msg.message_name(), msg.dump());
|
||||
#endif
|
||||
return this->send_message_(msg, T::MESSAGE_TYPE);
|
||||
}
|
||||
|
||||
@@ -526,8 +526,8 @@ void APIServer::on_shutdown() {
|
||||
for (auto &c : this->clients_) {
|
||||
if (!c->send_message(DisconnectRequest())) {
|
||||
// If we can't send the disconnect request directly (tx_buffer full),
|
||||
// schedule it at the front of the batch so it will be sent with priority
|
||||
c->schedule_message_front_(nullptr, &APIConnection::try_send_disconnect_request, DisconnectRequest::MESSAGE_TYPE);
|
||||
// schedule it in the batch so it will be sent with the 5ms timer
|
||||
c->schedule_message_(nullptr, &APIConnection::try_send_disconnect_request, DisconnectRequest::MESSAGE_TYPE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,6 +335,7 @@ class ProtoMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
std::string dump() const;
|
||||
virtual void dump_to(std::string &out) const = 0;
|
||||
virtual const char *message_name() const { return "unknown"; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
|
||||
@@ -4,7 +4,7 @@ import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from esphome import git
|
||||
from esphome import yaml_util
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -23,7 +23,6 @@ from esphome.const import (
|
||||
CONF_REFRESH,
|
||||
CONF_SOURCE,
|
||||
CONF_TYPE,
|
||||
CONF_URL,
|
||||
CONF_VARIANT,
|
||||
CONF_VERSION,
|
||||
KEY_CORE,
|
||||
@@ -32,14 +31,13 @@ from esphome.const import (
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
KEY_TARGET_PLATFORM,
|
||||
PLATFORM_ESP32,
|
||||
TYPE_GIT,
|
||||
TYPE_LOCAL,
|
||||
__version__,
|
||||
)
|
||||
from esphome.core import CORE, HexInt, TimePeriod
|
||||
from esphome.cpp_generator import RawExpression
|
||||
import esphome.final_validate as fv
|
||||
from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .boards import BOARDS
|
||||
from .const import ( # noqa
|
||||
@@ -49,10 +47,8 @@ from .const import ( # noqa
|
||||
KEY_EXTRA_BUILD_FILES,
|
||||
KEY_PATH,
|
||||
KEY_REF,
|
||||
KEY_REFRESH,
|
||||
KEY_REPO,
|
||||
KEY_SDKCONFIG_OPTIONS,
|
||||
KEY_SUBMODULES,
|
||||
KEY_VARIANT,
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C2,
|
||||
@@ -235,7 +231,7 @@ def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType):
|
||||
def add_idf_component(
|
||||
*,
|
||||
name: str,
|
||||
repo: str,
|
||||
repo: str = None,
|
||||
ref: str = None,
|
||||
path: str = None,
|
||||
refresh: TimePeriod = None,
|
||||
@@ -245,30 +241,27 @@ def add_idf_component(
|
||||
"""Add an esp-idf component to the project."""
|
||||
if not CORE.using_esp_idf:
|
||||
raise ValueError("Not an esp-idf project")
|
||||
if components is None:
|
||||
components = []
|
||||
if name not in CORE.data[KEY_ESP32][KEY_COMPONENTS]:
|
||||
if not repo and not ref and not path:
|
||||
raise ValueError("Requires at least one of repo, ref or path")
|
||||
if refresh or submodules or components:
|
||||
_LOGGER.warning(
|
||||
"The refresh, components and submodules parameters in add_idf_component() are "
|
||||
"deprecated and will be removed in ESPHome 2026.1. If you are seeing this, report "
|
||||
"an issue to the external_component author and ask them to update it."
|
||||
)
|
||||
if components:
|
||||
for comp in components:
|
||||
CORE.data[KEY_ESP32][KEY_COMPONENTS][comp] = {
|
||||
KEY_REPO: repo,
|
||||
KEY_REF: ref,
|
||||
KEY_PATH: f"{path}/{comp}" if path else comp,
|
||||
}
|
||||
else:
|
||||
CORE.data[KEY_ESP32][KEY_COMPONENTS][name] = {
|
||||
KEY_REPO: repo,
|
||||
KEY_REF: ref,
|
||||
KEY_PATH: path,
|
||||
KEY_REFRESH: refresh,
|
||||
KEY_COMPONENTS: components,
|
||||
KEY_SUBMODULES: submodules,
|
||||
}
|
||||
else:
|
||||
component_config = CORE.data[KEY_ESP32][KEY_COMPONENTS][name]
|
||||
if components is not None:
|
||||
component_config[KEY_COMPONENTS] = list(
|
||||
set(component_config[KEY_COMPONENTS] + components)
|
||||
)
|
||||
if submodules is not None:
|
||||
if component_config[KEY_SUBMODULES] is None:
|
||||
component_config[KEY_SUBMODULES] = submodules
|
||||
else:
|
||||
component_config[KEY_SUBMODULES] = list(
|
||||
set(component_config[KEY_SUBMODULES] + submodules)
|
||||
)
|
||||
|
||||
|
||||
def add_extra_script(stage: str, filename: str, path: str):
|
||||
@@ -575,6 +568,17 @@ CONF_ENABLE_LWIP_DHCP_SERVER = "enable_lwip_dhcp_server"
|
||||
CONF_ENABLE_LWIP_MDNS_QUERIES = "enable_lwip_mdns_queries"
|
||||
CONF_ENABLE_LWIP_BRIDGE_INTERFACE = "enable_lwip_bridge_interface"
|
||||
|
||||
|
||||
def _validate_idf_component(config: ConfigType) -> ConfigType:
|
||||
"""Validate IDF component config and warn about deprecated options."""
|
||||
if CONF_REFRESH in config:
|
||||
_LOGGER.warning(
|
||||
"The 'refresh' option for IDF components is deprecated and has no effect. "
|
||||
"It will be removed in ESPHome 2026.1. Please remove it from your configuration."
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
@@ -614,15 +618,19 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.string_strict,
|
||||
cv.Required(CONF_SOURCE): cv.SOURCE_SCHEMA,
|
||||
cv.Optional(CONF_PATH): cv.string,
|
||||
cv.Optional(CONF_REFRESH, default="1d"): cv.All(
|
||||
cv.string, cv.source_refresh
|
||||
),
|
||||
}
|
||||
cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.string_strict,
|
||||
cv.Optional(CONF_SOURCE): cv.git_ref,
|
||||
cv.Optional(CONF_REF): cv.string,
|
||||
cv.Optional(CONF_PATH): cv.string,
|
||||
cv.Optional(CONF_REFRESH): cv.All(
|
||||
cv.string, cv.source_refresh
|
||||
),
|
||||
}
|
||||
),
|
||||
_validate_idf_component,
|
||||
)
|
||||
),
|
||||
}
|
||||
@@ -814,18 +822,12 @@ async def to_code(config):
|
||||
add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
|
||||
|
||||
for component in conf[CONF_COMPONENTS]:
|
||||
source = component[CONF_SOURCE]
|
||||
if source[CONF_TYPE] == TYPE_GIT:
|
||||
add_idf_component(
|
||||
name=component[CONF_NAME],
|
||||
repo=source[CONF_URL],
|
||||
ref=source.get(CONF_REF),
|
||||
path=component.get(CONF_PATH),
|
||||
refresh=component[CONF_REFRESH],
|
||||
)
|
||||
elif source[CONF_TYPE] == TYPE_LOCAL:
|
||||
_LOGGER.warning("Local components are not implemented yet.")
|
||||
|
||||
add_idf_component(
|
||||
name=component[CONF_NAME],
|
||||
repo=component.get(CONF_SOURCE),
|
||||
ref=component.get(CONF_REF),
|
||||
path=component.get(CONF_PATH),
|
||||
)
|
||||
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
|
||||
cg.add_platformio_option("framework", "arduino")
|
||||
cg.add_build_flag("-DUSE_ARDUINO")
|
||||
@@ -924,6 +926,26 @@ def _write_sdkconfig():
|
||||
write_file_if_changed(sdk_path, contents)
|
||||
|
||||
|
||||
def _write_idf_component_yml():
|
||||
yml_path = Path(CORE.relative_build_path("src/idf_component.yml"))
|
||||
if CORE.data[KEY_ESP32][KEY_COMPONENTS]:
|
||||
components: dict = CORE.data[KEY_ESP32][KEY_COMPONENTS]
|
||||
dependencies = {}
|
||||
for name, component in components.items():
|
||||
dependency = {}
|
||||
if component[KEY_REF]:
|
||||
dependency["version"] = component[KEY_REF]
|
||||
if component[KEY_REPO]:
|
||||
dependency["git"] = component[KEY_REPO]
|
||||
if component[KEY_PATH]:
|
||||
dependency["path"] = component[KEY_PATH]
|
||||
dependencies[name] = dependency
|
||||
contents = yaml_util.dump({"dependencies": dependencies})
|
||||
else:
|
||||
contents = ""
|
||||
write_file_if_changed(yml_path, contents)
|
||||
|
||||
|
||||
# Called by writer.py
|
||||
def copy_files():
|
||||
if CORE.using_arduino:
|
||||
@@ -936,6 +958,7 @@ def copy_files():
|
||||
)
|
||||
if CORE.using_esp_idf:
|
||||
_write_sdkconfig()
|
||||
_write_idf_component_yml()
|
||||
if "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]:
|
||||
write_file_if_changed(
|
||||
CORE.relative_build_path("partitions.csv"),
|
||||
@@ -952,55 +975,6 @@ def copy_files():
|
||||
__version__,
|
||||
)
|
||||
|
||||
import shutil
|
||||
|
||||
shutil.rmtree(CORE.relative_build_path("components"), ignore_errors=True)
|
||||
|
||||
if CORE.data[KEY_ESP32][KEY_COMPONENTS]:
|
||||
components: dict = CORE.data[KEY_ESP32][KEY_COMPONENTS]
|
||||
|
||||
for name, component in components.items():
|
||||
repo_dir, _ = git.clone_or_update(
|
||||
url=component[KEY_REPO],
|
||||
ref=component[KEY_REF],
|
||||
refresh=component[KEY_REFRESH],
|
||||
domain="idf_components",
|
||||
submodules=component[KEY_SUBMODULES],
|
||||
)
|
||||
mkdir_p(CORE.relative_build_path("components"))
|
||||
component_dir = repo_dir
|
||||
if component[KEY_PATH] is not None:
|
||||
component_dir = component_dir / component[KEY_PATH]
|
||||
|
||||
if component[KEY_COMPONENTS] == ["*"]:
|
||||
shutil.copytree(
|
||||
component_dir,
|
||||
CORE.relative_build_path("components"),
|
||||
dirs_exist_ok=True,
|
||||
ignore=shutil.ignore_patterns(".git*"),
|
||||
symlinks=True,
|
||||
ignore_dangling_symlinks=True,
|
||||
)
|
||||
elif len(component[KEY_COMPONENTS]) > 0:
|
||||
for comp in component[KEY_COMPONENTS]:
|
||||
shutil.copytree(
|
||||
component_dir / comp,
|
||||
CORE.relative_build_path(f"components/{comp}"),
|
||||
dirs_exist_ok=True,
|
||||
ignore=shutil.ignore_patterns(".git*"),
|
||||
symlinks=True,
|
||||
ignore_dangling_symlinks=True,
|
||||
)
|
||||
else:
|
||||
shutil.copytree(
|
||||
component_dir,
|
||||
CORE.relative_build_path(f"components/{name}"),
|
||||
dirs_exist_ok=True,
|
||||
ignore=shutil.ignore_patterns(".git*"),
|
||||
symlinks=True,
|
||||
ignore_dangling_symlinks=True,
|
||||
)
|
||||
|
||||
for _, file in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES].items():
|
||||
if file[KEY_PATH].startswith("http"):
|
||||
import requests
|
||||
|
||||
@@ -10,24 +10,11 @@ GPIOBinarySensor = gpio_ns.class_(
|
||||
"GPIOBinarySensor", binary_sensor.BinarySensor, cg.Component
|
||||
)
|
||||
|
||||
CONF_USE_INTERRUPT = "use_interrupt"
|
||||
CONF_INTERRUPT_TYPE = "interrupt_type"
|
||||
|
||||
INTERRUPT_TYPES = {
|
||||
"RISING": gpio_ns.INTERRUPT_RISING_EDGE,
|
||||
"FALLING": gpio_ns.INTERRUPT_FALLING_EDGE,
|
||||
"ANY": gpio_ns.INTERRUPT_ANY_EDGE,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
binary_sensor.binary_sensor_schema(GPIOBinarySensor)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_PIN): pins.gpio_input_pin_schema,
|
||||
cv.Optional(CONF_USE_INTERRUPT, default=True): cv.boolean,
|
||||
cv.Optional(CONF_INTERRUPT_TYPE, default="ANY"): cv.enum(
|
||||
INTERRUPT_TYPES, upper=True
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
@@ -40,7 +27,3 @@ 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]:
|
||||
cg.add(var.set_interrupt_type(INTERRUPT_TYPES[config[CONF_INTERRUPT_TYPE]]))
|
||||
|
||||
@@ -6,91 +6,17 @@ namespace gpio {
|
||||
|
||||
static const char *const TAG = "gpio.binary_sensor";
|
||||
|
||||
void IRAM_ATTR GPIOBinarySensorStore::gpio_intr(GPIOBinarySensorStore *arg) {
|
||||
bool new_state = arg->isr_pin_.digital_read();
|
||||
if (new_state != arg->last_state_) {
|
||||
arg->state_ = new_state;
|
||||
arg->last_state_ = new_state;
|
||||
arg->changed_ = true;
|
||||
// Wake up the component from its disabled loop state
|
||||
if (arg->component_ != nullptr) {
|
||||
arg->component_->enable_loop_soon_any_context();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GPIOBinarySensorStore::setup(InternalGPIOPin *pin, gpio::InterruptType type, Component *component) {
|
||||
pin->setup();
|
||||
this->isr_pin_ = pin->to_isr();
|
||||
this->component_ = component;
|
||||
|
||||
// Read initial state
|
||||
this->last_state_ = pin->digital_read();
|
||||
this->state_ = this->last_state_;
|
||||
|
||||
// Attach interrupt - from this point on, any changes will be caught by the interrupt
|
||||
pin->attach_interrupt(&GPIOBinarySensorStore::gpio_intr, this, type);
|
||||
}
|
||||
|
||||
void GPIOBinarySensor::setup() {
|
||||
if (this->use_interrupt_ && !this->pin_->is_internal()) {
|
||||
ESP_LOGD(TAG, "GPIO is not internal, falling back to polling mode");
|
||||
this->use_interrupt_ = false;
|
||||
}
|
||||
|
||||
if (this->use_interrupt_) {
|
||||
auto *internal_pin = static_cast<InternalGPIOPin *>(this->pin_);
|
||||
this->store_.setup(internal_pin, this->interrupt_type_, this);
|
||||
this->publish_initial_state(this->store_.get_state());
|
||||
} else {
|
||||
this->pin_->setup();
|
||||
this->publish_initial_state(this->pin_->digital_read());
|
||||
}
|
||||
this->pin_->setup();
|
||||
this->publish_initial_state(this->pin_->digital_read());
|
||||
}
|
||||
|
||||
void GPIOBinarySensor::dump_config() {
|
||||
LOG_BINARY_SENSOR("", "GPIO Binary Sensor", this);
|
||||
LOG_PIN(" Pin: ", this->pin_);
|
||||
const char *mode = this->use_interrupt_ ? "interrupt" : "polling";
|
||||
ESP_LOGCONFIG(TAG, " Mode: %s", mode);
|
||||
if (this->use_interrupt_) {
|
||||
const char *interrupt_type;
|
||||
switch (this->interrupt_type_) {
|
||||
case gpio::INTERRUPT_RISING_EDGE:
|
||||
interrupt_type = "RISING_EDGE";
|
||||
break;
|
||||
case gpio::INTERRUPT_FALLING_EDGE:
|
||||
interrupt_type = "FALLING_EDGE";
|
||||
break;
|
||||
case gpio::INTERRUPT_ANY_EDGE:
|
||||
interrupt_type = "ANY_EDGE";
|
||||
break;
|
||||
default:
|
||||
interrupt_type = "UNKNOWN";
|
||||
break;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Interrupt Type: %s", interrupt_type);
|
||||
}
|
||||
}
|
||||
|
||||
void GPIOBinarySensor::loop() {
|
||||
if (this->use_interrupt_) {
|
||||
if (this->store_.is_changed()) {
|
||||
// Clear the flag immediately to minimize the window where we might miss changes
|
||||
this->store_.clear_changed();
|
||||
// Read the state and publish it
|
||||
// Note: If the ISR fires between clear_changed() and get_state(), that's fine -
|
||||
// we'll process the new change on the next loop iteration
|
||||
bool state = this->store_.get_state();
|
||||
this->publish_state(state);
|
||||
} else {
|
||||
// No changes, disable the loop until the next interrupt
|
||||
this->disable_loop();
|
||||
}
|
||||
} else {
|
||||
this->publish_state(this->pin_->digital_read());
|
||||
}
|
||||
}
|
||||
void GPIOBinarySensor::loop() { this->publish_state(this->pin_->digital_read()); }
|
||||
|
||||
float GPIOBinarySensor::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
|
||||
@@ -2,51 +2,14 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace gpio {
|
||||
|
||||
// Store class for ISR data (no vtables, ISR-safe)
|
||||
class GPIOBinarySensorStore {
|
||||
public:
|
||||
void setup(InternalGPIOPin *pin, gpio::InterruptType type, Component *component);
|
||||
|
||||
static void gpio_intr(GPIOBinarySensorStore *arg);
|
||||
|
||||
bool get_state() const {
|
||||
// No lock needed: state_ is atomically updated by ISR
|
||||
// Volatile ensures we read the latest value
|
||||
return this->state_;
|
||||
}
|
||||
|
||||
bool is_changed() const {
|
||||
// Simple read of volatile bool - no clearing here
|
||||
return this->changed_;
|
||||
}
|
||||
|
||||
void clear_changed() {
|
||||
// Separate method to clear the flag
|
||||
this->changed_ = false;
|
||||
}
|
||||
|
||||
protected:
|
||||
ISRInternalGPIOPin isr_pin_;
|
||||
volatile bool state_{false};
|
||||
volatile bool last_state_{false};
|
||||
volatile bool changed_{false};
|
||||
Component *component_{nullptr}; // Pointer to the component for enable_loop_soon_any_context()
|
||||
};
|
||||
|
||||
class GPIOBinarySensor : public binary_sensor::BinarySensor, public Component {
|
||||
public:
|
||||
// No destructor needed: ESPHome components are created at boot and live forever.
|
||||
// Interrupts are only detached on reboot when memory is cleared anyway.
|
||||
|
||||
void set_pin(GPIOPin *pin) { pin_ = pin; }
|
||||
void set_use_interrupt(bool use_interrupt) { use_interrupt_ = use_interrupt; }
|
||||
void set_interrupt_type(gpio::InterruptType type) { interrupt_type_ = type; }
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
/// Setup pin
|
||||
@@ -59,9 +22,6 @@ class GPIOBinarySensor : public binary_sensor::BinarySensor, public Component {
|
||||
|
||||
protected:
|
||||
GPIOPin *pin_;
|
||||
bool use_interrupt_{true};
|
||||
gpio::InterruptType interrupt_type_{gpio::INTERRUPT_ANY_EDGE};
|
||||
GPIOBinarySensorStore store_;
|
||||
};
|
||||
|
||||
} // namespace gpio
|
||||
|
||||
@@ -17,8 +17,106 @@ namespace esphome {
|
||||
namespace ld2410 {
|
||||
|
||||
static const char *const TAG = "ld2410";
|
||||
static const char *const NO_MAC = "08:05:04:03:02:01";
|
||||
static const char *const UNKNOWN_MAC = "unknown";
|
||||
static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X";
|
||||
|
||||
LD2410Component::LD2410Component() {}
|
||||
// Memory-efficient lookup tables
|
||||
namespace {
|
||||
struct StringToUint8 {
|
||||
const char *str;
|
||||
uint8_t value;
|
||||
};
|
||||
|
||||
struct Uint8ToString {
|
||||
uint8_t value;
|
||||
const char *str;
|
||||
};
|
||||
|
||||
constexpr StringToUint8 BAUD_RATES[] = {
|
||||
{"9600", BAUD_RATE_9600}, {"19200", BAUD_RATE_19200}, {"38400", BAUD_RATE_38400},
|
||||
{"57600", BAUD_RATE_57600}, {"115200", BAUD_RATE_115200}, {"230400", BAUD_RATE_230400},
|
||||
{"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800},
|
||||
};
|
||||
|
||||
constexpr StringToUint8 DISTANCE_RESOLUTIONS[] = {
|
||||
{"0.2m", DISTANCE_RESOLUTION_0_2},
|
||||
{"0.75m", DISTANCE_RESOLUTION_0_75},
|
||||
};
|
||||
|
||||
constexpr Uint8ToString DISTANCE_RESOLUTIONS_REV[] = {
|
||||
{DISTANCE_RESOLUTION_0_2, "0.2m"},
|
||||
{DISTANCE_RESOLUTION_0_75, "0.75m"},
|
||||
};
|
||||
|
||||
constexpr StringToUint8 LIGHT_FUNCTIONS[] = {
|
||||
{"off", LIGHT_FUNCTION_OFF},
|
||||
{"below", LIGHT_FUNCTION_BELOW},
|
||||
{"above", LIGHT_FUNCTION_ABOVE},
|
||||
};
|
||||
|
||||
constexpr Uint8ToString LIGHT_FUNCTIONS_REV[] = {
|
||||
{LIGHT_FUNCTION_OFF, "off"},
|
||||
{LIGHT_FUNCTION_BELOW, "below"},
|
||||
{LIGHT_FUNCTION_ABOVE, "above"},
|
||||
};
|
||||
|
||||
constexpr StringToUint8 OUT_PIN_LEVELS[] = {
|
||||
{"low", OUT_PIN_LEVEL_LOW},
|
||||
{"high", OUT_PIN_LEVEL_HIGH},
|
||||
};
|
||||
|
||||
constexpr Uint8ToString OUT_PIN_LEVELS_REV[] = {
|
||||
{OUT_PIN_LEVEL_LOW, "low"},
|
||||
{OUT_PIN_LEVEL_HIGH, "high"},
|
||||
};
|
||||
|
||||
// Helper functions 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)
|
||||
return entry.value;
|
||||
}
|
||||
return 0xFF; // Not found
|
||||
}
|
||||
|
||||
template<size_t N> const char *find_str(const Uint8ToString (&arr)[N], uint8_t value) {
|
||||
for (const auto &entry : arr) {
|
||||
if (value == entry.value)
|
||||
return entry.str;
|
||||
}
|
||||
return ""; // Not found
|
||||
}
|
||||
} // namespace
|
||||
// Commands
|
||||
static const uint8_t CMD_ENABLE_CONF = 0xFF;
|
||||
static const uint8_t CMD_DISABLE_CONF = 0xFE;
|
||||
static const uint8_t CMD_ENABLE_ENG = 0x62;
|
||||
static const uint8_t CMD_DISABLE_ENG = 0x63;
|
||||
static const uint8_t CMD_MAXDIST_DURATION = 0x60;
|
||||
static const uint8_t CMD_QUERY = 0x61;
|
||||
static const uint8_t CMD_GATE_SENS = 0x64;
|
||||
static const uint8_t CMD_VERSION = 0xA0;
|
||||
static const uint8_t CMD_QUERY_DISTANCE_RESOLUTION = 0xAB;
|
||||
static const uint8_t CMD_SET_DISTANCE_RESOLUTION = 0xAA;
|
||||
static const uint8_t CMD_QUERY_LIGHT_CONTROL = 0xAE;
|
||||
static const uint8_t CMD_SET_LIGHT_CONTROL = 0xAD;
|
||||
static const uint8_t CMD_SET_BAUD_RATE = 0xA1;
|
||||
static const uint8_t CMD_BT_PASSWORD = 0xA9;
|
||||
static const uint8_t CMD_MAC = 0xA5;
|
||||
static const uint8_t CMD_RESET = 0xA2;
|
||||
static const uint8_t CMD_RESTART = 0xA3;
|
||||
static const uint8_t CMD_BLUETOOTH = 0xA4;
|
||||
// Commands values
|
||||
static const uint8_t CMD_MAX_MOVE_VALUE = 0x00;
|
||||
static const uint8_t CMD_MAX_STILL_VALUE = 0x01;
|
||||
static const uint8_t CMD_DURATION_VALUE = 0x02;
|
||||
// Command Header & Footer
|
||||
static const uint8_t CMD_FRAME_HEADER[4] = {0xFD, 0xFC, 0xFB, 0xFA};
|
||||
static const uint8_t CMD_FRAME_END[4] = {0x04, 0x03, 0x02, 0x01};
|
||||
// Data Header & Footer
|
||||
static const uint8_t DATA_FRAME_HEADER[4] = {0xF4, 0xF3, 0xF2, 0xF1};
|
||||
static const uint8_t DATA_FRAME_END[4] = {0xF8, 0xF7, 0xF6, 0xF5};
|
||||
|
||||
void LD2410Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "LD2410:");
|
||||
@@ -78,7 +176,7 @@ void LD2410Component::dump_config() {
|
||||
" Throttle: %ums\n"
|
||||
" MAC address: %s\n"
|
||||
" Firmware version: %s",
|
||||
this->throttle_, const_cast<char *>(this->mac_.c_str()), const_cast<char *>(this->version_.c_str()));
|
||||
this->throttle_, this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_.c_str(), this->version_.c_str());
|
||||
}
|
||||
|
||||
void LD2410Component::setup() {
|
||||
@@ -200,7 +298,7 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) {
|
||||
*/
|
||||
#ifdef USE_SENSOR
|
||||
if (this->moving_target_distance_sensor_ != nullptr) {
|
||||
int new_moving_target_distance = this->two_byte_to_int_(buffer[MOVING_TARGET_LOW], buffer[MOVING_TARGET_HIGH]);
|
||||
int new_moving_target_distance = ld2410::two_byte_to_int(buffer[MOVING_TARGET_LOW], buffer[MOVING_TARGET_HIGH]);
|
||||
if (this->moving_target_distance_sensor_->get_state() != new_moving_target_distance)
|
||||
this->moving_target_distance_sensor_->publish_state(new_moving_target_distance);
|
||||
}
|
||||
@@ -210,7 +308,7 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) {
|
||||
this->moving_target_energy_sensor_->publish_state(new_moving_target_energy);
|
||||
}
|
||||
if (this->still_target_distance_sensor_ != nullptr) {
|
||||
int new_still_target_distance = this->two_byte_to_int_(buffer[STILL_TARGET_LOW], buffer[STILL_TARGET_HIGH]);
|
||||
int new_still_target_distance = ld2410::two_byte_to_int(buffer[STILL_TARGET_LOW], buffer[STILL_TARGET_HIGH]);
|
||||
if (this->still_target_distance_sensor_->get_state() != new_still_target_distance)
|
||||
this->still_target_distance_sensor_->publish_state(new_still_target_distance);
|
||||
}
|
||||
@@ -220,7 +318,7 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) {
|
||||
this->still_target_energy_sensor_->publish_state(new_still_target_energy);
|
||||
}
|
||||
if (this->detection_distance_sensor_ != nullptr) {
|
||||
int new_detect_distance = this->two_byte_to_int_(buffer[DETECT_DISTANCE_LOW], buffer[DETECT_DISTANCE_HIGH]);
|
||||
int new_detect_distance = ld2410::two_byte_to_int(buffer[DETECT_DISTANCE_LOW], buffer[DETECT_DISTANCE_HIGH]);
|
||||
if (this->detection_distance_sensor_->get_state() != new_detect_distance)
|
||||
this->detection_distance_sensor_->publish_state(new_detect_distance);
|
||||
}
|
||||
@@ -282,25 +380,6 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) {
|
||||
#endif
|
||||
}
|
||||
|
||||
const char VERSION_FMT[] = "%u.%02X.%02X%02X%02X%02X";
|
||||
|
||||
std::string format_version(uint8_t *buffer) {
|
||||
std::string::size_type version_size = 256;
|
||||
std::string version;
|
||||
do {
|
||||
version.resize(version_size + 1);
|
||||
version_size = std::snprintf(&version[0], version.size(), VERSION_FMT, buffer[13], buffer[12], buffer[17],
|
||||
buffer[16], buffer[15], buffer[14]);
|
||||
} while (version_size + 1 > version.size());
|
||||
version.resize(version_size);
|
||||
return version;
|
||||
}
|
||||
|
||||
const char MAC_FMT[] = "%02X:%02X:%02X:%02X:%02X:%02X";
|
||||
|
||||
const std::string UNKNOWN_MAC("unknown");
|
||||
const std::string NO_MAC("08:05:04:03:02:01");
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
std::function<void(void)> set_number_value(number::Number *n, float value) {
|
||||
float normalized_value = value * 1.0;
|
||||
@@ -326,7 +405,7 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
|
||||
ESP_LOGE(TAG, "Invalid status");
|
||||
return true;
|
||||
}
|
||||
if (this->two_byte_to_int_(buffer[8], buffer[9]) != 0x00) {
|
||||
if (ld2410::two_byte_to_int(buffer[8], buffer[9]) != 0x00) {
|
||||
ESP_LOGE(TAG, "Invalid command: %u, %u", buffer[8], buffer[9]);
|
||||
return true;
|
||||
}
|
||||
@@ -347,8 +426,8 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
|
||||
#endif
|
||||
break;
|
||||
case lowbyte(CMD_VERSION):
|
||||
this->version_ = format_version(buffer);
|
||||
ESP_LOGV(TAG, "Firmware version: %s", const_cast<char *>(this->version_.c_str()));
|
||||
this->version_ = str_sprintf(VERSION_FMT, buffer[13], buffer[12], buffer[17], buffer[16], buffer[15], buffer[14]);
|
||||
ESP_LOGV(TAG, "Firmware version: %s", this->version_.c_str());
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->version_text_sensor_ != nullptr) {
|
||||
this->version_text_sensor_->publish_state(this->version_);
|
||||
@@ -357,8 +436,8 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
|
||||
break;
|
||||
case lowbyte(CMD_QUERY_DISTANCE_RESOLUTION): {
|
||||
std::string distance_resolution =
|
||||
DISTANCE_RESOLUTION_INT_TO_ENUM.at(this->two_byte_to_int_(buffer[10], buffer[11]));
|
||||
ESP_LOGV(TAG, "Distance resolution: %s", const_cast<char *>(distance_resolution.c_str()));
|
||||
find_str(DISTANCE_RESOLUTIONS_REV, ld2410::two_byte_to_int(buffer[10], buffer[11]));
|
||||
ESP_LOGV(TAG, "Distance resolution: %s", distance_resolution.c_str());
|
||||
#ifdef USE_SELECT
|
||||
if (this->distance_resolution_select_ != nullptr &&
|
||||
this->distance_resolution_select_->state != distance_resolution) {
|
||||
@@ -367,9 +446,9 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
|
||||
#endif
|
||||
} break;
|
||||
case lowbyte(CMD_QUERY_LIGHT_CONTROL): {
|
||||
this->light_function_ = LIGHT_FUNCTION_INT_TO_ENUM.at(buffer[10]);
|
||||
this->light_function_ = find_str(LIGHT_FUNCTIONS_REV, buffer[10]);
|
||||
this->light_threshold_ = buffer[11] * 1.0;
|
||||
this->out_pin_level_ = OUT_PIN_LEVEL_INT_TO_ENUM.at(buffer[12]);
|
||||
this->out_pin_level_ = find_str(OUT_PIN_LEVELS_REV, buffer[12]);
|
||||
ESP_LOGV(TAG, "Light function: %s", const_cast<char *>(this->light_function_.c_str()));
|
||||
ESP_LOGV(TAG, "Light threshold: %f", this->light_threshold_);
|
||||
ESP_LOGV(TAG, "Out pin level: %s", const_cast<char *>(this->out_pin_level_.c_str()));
|
||||
@@ -402,7 +481,7 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
if (this->bluetooth_switch_ != nullptr) {
|
||||
this->bluetooth_switch_->publish_state(this->mac_ != UNKNOWN_MAC);
|
||||
this->bluetooth_switch_->publish_state(this->mac_ != NO_MAC);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
@@ -448,7 +527,7 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
|
||||
/*
|
||||
None Duration: 33~34th bytes
|
||||
*/
|
||||
updates.push_back(set_number_value(this->timeout_number_, this->two_byte_to_int_(buffer[32], buffer[33])));
|
||||
updates.push_back(set_number_value(this->timeout_number_, ld2410::two_byte_to_int(buffer[32], buffer[33])));
|
||||
for (auto &update : updates) {
|
||||
update();
|
||||
}
|
||||
@@ -505,14 +584,14 @@ void LD2410Component::set_bluetooth(bool enable) {
|
||||
|
||||
void LD2410Component::set_distance_resolution(const std::string &state) {
|
||||
this->set_config_mode_(true);
|
||||
uint8_t cmd_value[2] = {DISTANCE_RESOLUTION_ENUM_TO_INT.at(state), 0x00};
|
||||
uint8_t cmd_value[2] = {find_uint8(DISTANCE_RESOLUTIONS, state), 0x00};
|
||||
this->send_command_(CMD_SET_DISTANCE_RESOLUTION, cmd_value, 2);
|
||||
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
|
||||
}
|
||||
|
||||
void LD2410Component::set_baud_rate(const std::string &state) {
|
||||
this->set_config_mode_(true);
|
||||
uint8_t cmd_value[2] = {BAUD_RATE_ENUM_TO_INT.at(state), 0x00};
|
||||
uint8_t cmd_value[2] = {find_uint8(BAUD_RATES, state), 0x00};
|
||||
this->send_command_(CMD_SET_BAUD_RATE, cmd_value, 2);
|
||||
this->set_timeout(200, [this]() { this->restart_(); });
|
||||
}
|
||||
@@ -646,9 +725,9 @@ void LD2410Component::set_light_out_control() {
|
||||
return;
|
||||
}
|
||||
this->set_config_mode_(true);
|
||||
uint8_t light_function = LIGHT_FUNCTION_ENUM_TO_INT.at(this->light_function_);
|
||||
uint8_t light_function = find_uint8(LIGHT_FUNCTIONS, this->light_function_);
|
||||
uint8_t light_threshold = static_cast<uint8_t>(this->light_threshold_);
|
||||
uint8_t out_pin_level = OUT_PIN_LEVEL_ENUM_TO_INT.at(this->out_pin_level_);
|
||||
uint8_t out_pin_level = find_uint8(OUT_PIN_LEVELS, this->out_pin_level_);
|
||||
uint8_t value[4] = {light_function, light_threshold, out_pin_level, 0x00};
|
||||
this->send_command_(CMD_SET_LIGHT_CONTROL, value, 4);
|
||||
delay(50); // NOLINT
|
||||
|
||||
@@ -26,33 +26,11 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2410 {
|
||||
|
||||
#define CHECK_BIT(var, pos) (((var) >> (pos)) & 1)
|
||||
|
||||
// Commands
|
||||
static const uint8_t CMD_ENABLE_CONF = 0x00FF;
|
||||
static const uint8_t CMD_DISABLE_CONF = 0x00FE;
|
||||
static const uint8_t CMD_ENABLE_ENG = 0x0062;
|
||||
static const uint8_t CMD_DISABLE_ENG = 0x0063;
|
||||
static const uint8_t CMD_MAXDIST_DURATION = 0x0060;
|
||||
static const uint8_t CMD_QUERY = 0x0061;
|
||||
static const uint8_t CMD_GATE_SENS = 0x0064;
|
||||
static const uint8_t CMD_VERSION = 0x00A0;
|
||||
static const uint8_t CMD_QUERY_DISTANCE_RESOLUTION = 0x00AB;
|
||||
static const uint8_t CMD_SET_DISTANCE_RESOLUTION = 0x00AA;
|
||||
static const uint8_t CMD_QUERY_LIGHT_CONTROL = 0x00AE;
|
||||
static const uint8_t CMD_SET_LIGHT_CONTROL = 0x00AD;
|
||||
static const uint8_t CMD_SET_BAUD_RATE = 0x00A1;
|
||||
static const uint8_t CMD_BT_PASSWORD = 0x00A9;
|
||||
static const uint8_t CMD_MAC = 0x00A5;
|
||||
static const uint8_t CMD_RESET = 0x00A2;
|
||||
static const uint8_t CMD_RESTART = 0x00A3;
|
||||
static const uint8_t CMD_BLUETOOTH = 0x00A4;
|
||||
|
||||
enum BaudRateStructure : uint8_t {
|
||||
BAUD_RATE_9600 = 1,
|
||||
BAUD_RATE_19200 = 2,
|
||||
@@ -61,58 +39,25 @@ enum BaudRateStructure : uint8_t {
|
||||
BAUD_RATE_115200 = 5,
|
||||
BAUD_RATE_230400 = 6,
|
||||
BAUD_RATE_256000 = 7,
|
||||
BAUD_RATE_460800 = 8
|
||||
BAUD_RATE_460800 = 8,
|
||||
};
|
||||
|
||||
static const std::map<std::string, uint8_t> BAUD_RATE_ENUM_TO_INT{
|
||||
{"9600", BAUD_RATE_9600}, {"19200", BAUD_RATE_19200}, {"38400", BAUD_RATE_38400},
|
||||
{"57600", BAUD_RATE_57600}, {"115200", BAUD_RATE_115200}, {"230400", BAUD_RATE_230400},
|
||||
{"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800}};
|
||||
|
||||
enum DistanceResolutionStructure : uint8_t { DISTANCE_RESOLUTION_0_2 = 0x01, DISTANCE_RESOLUTION_0_75 = 0x00 };
|
||||
|
||||
static const std::map<std::string, uint8_t> DISTANCE_RESOLUTION_ENUM_TO_INT{{"0.2m", DISTANCE_RESOLUTION_0_2},
|
||||
{"0.75m", DISTANCE_RESOLUTION_0_75}};
|
||||
static const std::map<uint8_t, std::string> DISTANCE_RESOLUTION_INT_TO_ENUM{{DISTANCE_RESOLUTION_0_2, "0.2m"},
|
||||
{DISTANCE_RESOLUTION_0_75, "0.75m"}};
|
||||
enum DistanceResolutionStructure : uint8_t {
|
||||
DISTANCE_RESOLUTION_0_2 = 0x01,
|
||||
DISTANCE_RESOLUTION_0_75 = 0x00,
|
||||
};
|
||||
|
||||
enum LightFunctionStructure : uint8_t {
|
||||
LIGHT_FUNCTION_OFF = 0x00,
|
||||
LIGHT_FUNCTION_BELOW = 0x01,
|
||||
LIGHT_FUNCTION_ABOVE = 0x02
|
||||
LIGHT_FUNCTION_ABOVE = 0x02,
|
||||
};
|
||||
|
||||
static const std::map<std::string, uint8_t> LIGHT_FUNCTION_ENUM_TO_INT{
|
||||
{"off", LIGHT_FUNCTION_OFF}, {"below", LIGHT_FUNCTION_BELOW}, {"above", LIGHT_FUNCTION_ABOVE}};
|
||||
static const std::map<uint8_t, std::string> LIGHT_FUNCTION_INT_TO_ENUM{
|
||||
{LIGHT_FUNCTION_OFF, "off"}, {LIGHT_FUNCTION_BELOW, "below"}, {LIGHT_FUNCTION_ABOVE, "above"}};
|
||||
enum OutPinLevelStructure : uint8_t {
|
||||
OUT_PIN_LEVEL_LOW = 0x00,
|
||||
OUT_PIN_LEVEL_HIGH = 0x01,
|
||||
};
|
||||
|
||||
enum OutPinLevelStructure : uint8_t { OUT_PIN_LEVEL_LOW = 0x00, OUT_PIN_LEVEL_HIGH = 0x01 };
|
||||
|
||||
static const std::map<std::string, uint8_t> OUT_PIN_LEVEL_ENUM_TO_INT{{"low", OUT_PIN_LEVEL_LOW},
|
||||
{"high", OUT_PIN_LEVEL_HIGH}};
|
||||
static const std::map<uint8_t, std::string> OUT_PIN_LEVEL_INT_TO_ENUM{{OUT_PIN_LEVEL_LOW, "low"},
|
||||
{OUT_PIN_LEVEL_HIGH, "high"}};
|
||||
|
||||
// Commands values
|
||||
static const uint8_t CMD_MAX_MOVE_VALUE = 0x0000;
|
||||
static const uint8_t CMD_MAX_STILL_VALUE = 0x0001;
|
||||
static const uint8_t CMD_DURATION_VALUE = 0x0002;
|
||||
// Command Header & Footer
|
||||
static const uint8_t CMD_FRAME_HEADER[4] = {0xFD, 0xFC, 0xFB, 0xFA};
|
||||
static const uint8_t CMD_FRAME_END[4] = {0x04, 0x03, 0x02, 0x01};
|
||||
// Data Header & Footer
|
||||
static const uint8_t DATA_FRAME_HEADER[4] = {0xF4, 0xF3, 0xF2, 0xF1};
|
||||
static const uint8_t DATA_FRAME_END[4] = {0xF8, 0xF7, 0xF6, 0xF5};
|
||||
/*
|
||||
Data Type: 6th byte
|
||||
Target states: 9th byte
|
||||
Moving target distance: 10~11th bytes
|
||||
Moving target energy: 12th byte
|
||||
Still target distance: 13~14th bytes
|
||||
Still target energy: 15th byte
|
||||
Detect distance: 16~17th bytes
|
||||
*/
|
||||
enum PeriodicDataStructure : uint8_t {
|
||||
DATA_TYPES = 6,
|
||||
TARGET_STATES = 8,
|
||||
@@ -129,11 +74,20 @@ enum PeriodicDataStructure : uint8_t {
|
||||
LIGHT_SENSOR = 37,
|
||||
OUT_PIN_SENSOR = 38,
|
||||
};
|
||||
enum PeriodicDataValue : uint8_t { HEAD = 0xAA, END = 0x55, CHECK = 0x00 };
|
||||
|
||||
enum AckDataStructure : uint8_t { COMMAND = 6, COMMAND_STATUS = 7 };
|
||||
enum PeriodicDataValue : uint8_t {
|
||||
HEAD = 0xAA,
|
||||
END = 0x55,
|
||||
CHECK = 0x00,
|
||||
};
|
||||
|
||||
enum AckDataStructure : uint8_t {
|
||||
COMMAND = 6,
|
||||
COMMAND_STATUS = 7,
|
||||
};
|
||||
|
||||
static inline int two_byte_to_int(char firstbyte, char secondbyte) { return (int16_t) (secondbyte << 8) + firstbyte; }
|
||||
|
||||
// char cmd[2] = {enable ? 0xFF : 0xFE, 0x00};
|
||||
class LD2410Component : public Component, public uart::UARTDevice {
|
||||
#ifdef USE_SENSOR
|
||||
SUB_SENSOR(moving_target_distance)
|
||||
@@ -176,7 +130,6 @@ class LD2410Component : public Component, public uart::UARTDevice {
|
||||
#endif
|
||||
|
||||
public:
|
||||
LD2410Component();
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
@@ -202,7 +155,6 @@ class LD2410Component : public Component, public uart::UARTDevice {
|
||||
void factory_reset();
|
||||
|
||||
protected:
|
||||
int two_byte_to_int_(char firstbyte, char secondbyte) { return (int16_t) (secondbyte << 8) + firstbyte; }
|
||||
void send_command_(uint8_t command_str, const uint8_t *command_value, int command_value_len);
|
||||
void set_config_mode_(bool enable);
|
||||
void handle_periodic_data_(uint8_t *buffer, int len);
|
||||
@@ -215,14 +167,14 @@ class LD2410Component : public Component, public uart::UARTDevice {
|
||||
void get_light_control_();
|
||||
void restart_();
|
||||
|
||||
int32_t last_periodic_millis_ = millis();
|
||||
int32_t last_engineering_mode_change_millis_ = millis();
|
||||
int32_t last_periodic_millis_ = 0;
|
||||
int32_t last_engineering_mode_change_millis_ = 0;
|
||||
uint16_t throttle_;
|
||||
float light_threshold_ = -1;
|
||||
std::string version_;
|
||||
std::string mac_;
|
||||
std::string out_pin_level_;
|
||||
std::string light_function_;
|
||||
float light_threshold_ = -1;
|
||||
#ifdef USE_NUMBER
|
||||
std::vector<number::Number *> gate_still_threshold_numbers_ = std::vector<number::Number *>(9);
|
||||
std::vector<number::Number *> gate_move_threshold_numbers_ = std::vector<number::Number *>(9);
|
||||
|
||||
@@ -17,8 +17,9 @@ namespace esphome {
|
||||
namespace ld2450 {
|
||||
|
||||
static const char *const TAG = "ld2450";
|
||||
static const char *const NO_MAC("08:05:04:03:02:01");
|
||||
static const char *const UNKNOWN_MAC("unknown");
|
||||
static const char *const NO_MAC = "08:05:04:03:02:01";
|
||||
static const char *const UNKNOWN_MAC = "unknown";
|
||||
static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X";
|
||||
|
||||
// LD2450 UART Serial Commands
|
||||
static const uint8_t CMD_ENABLE_CONF = 0x00FF;
|
||||
@@ -98,13 +99,6 @@ static inline std::string get_direction(int16_t speed) {
|
||||
return STATIONARY;
|
||||
}
|
||||
|
||||
static inline std::string format_version(uint8_t *buffer) {
|
||||
return str_sprintf("%u.%02X.%02X%02X%02X%02X", buffer[13], buffer[12], buffer[17], buffer[16], buffer[15],
|
||||
buffer[14]);
|
||||
}
|
||||
|
||||
LD2450Component::LD2450Component() {}
|
||||
|
||||
void LD2450Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Running setup");
|
||||
#ifdef USE_NUMBER
|
||||
@@ -189,7 +183,7 @@ void LD2450Component::dump_config() {
|
||||
" Throttle: %ums\n"
|
||||
" MAC Address: %s\n"
|
||||
" Firmware version: %s",
|
||||
this->throttle_, const_cast<char *>(this->mac_.c_str()), const_cast<char *>(this->version_.c_str()));
|
||||
this->throttle_, this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_.c_str(), this->version_.c_str());
|
||||
}
|
||||
|
||||
void LD2450Component::loop() {
|
||||
@@ -596,7 +590,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
|
||||
#endif
|
||||
break;
|
||||
case lowbyte(CMD_VERSION):
|
||||
this->version_ = ld2450::format_version(buffer);
|
||||
this->version_ = str_sprintf(VERSION_FMT, buffer[13], buffer[12], buffer[17], buffer[16], buffer[15], buffer[14]);
|
||||
ESP_LOGV(TAG, "Firmware version: %s", this->version_.c_str());
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->version_text_sensor_ != nullptr) {
|
||||
@@ -617,7 +611,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
if (this->bluetooth_switch_ != nullptr) {
|
||||
this->bluetooth_switch_->publish_state(this->mac_ != UNKNOWN_MAC);
|
||||
this->bluetooth_switch_->publish_state(this->mac_ != NO_MAC);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
|
||||
@@ -141,7 +141,6 @@ class LD2450Component : public Component, public uart::UARTDevice {
|
||||
#endif
|
||||
|
||||
public:
|
||||
LD2450Component();
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
@@ -197,17 +196,17 @@ class LD2450Component : public Component, public uart::UARTDevice {
|
||||
bool get_timeout_status_(uint32_t check_millis);
|
||||
uint8_t count_targets_in_zone_(const Zone &zone, bool is_moving);
|
||||
|
||||
Target target_info_[MAX_TARGETS];
|
||||
Zone zone_config_[MAX_ZONES];
|
||||
uint8_t buffer_pos_ = 0; // where to resume processing/populating buffer
|
||||
uint8_t buffer_data_[MAX_LINE_LENGTH];
|
||||
uint32_t last_periodic_millis_ = 0;
|
||||
uint32_t presence_millis_ = 0;
|
||||
uint32_t still_presence_millis_ = 0;
|
||||
uint32_t moving_presence_millis_ = 0;
|
||||
uint16_t throttle_ = 0;
|
||||
uint16_t timeout_ = 5;
|
||||
uint8_t buffer_pos_ = 0; // where to resume processing/populating buffer
|
||||
uint8_t buffer_data_[MAX_LINE_LENGTH];
|
||||
uint8_t zone_type_ = 0;
|
||||
Target target_info_[MAX_TARGETS];
|
||||
Zone zone_config_[MAX_ZONES];
|
||||
std::string version_{};
|
||||
std::string mac_{};
|
||||
#ifdef USE_NUMBER
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
"""
|
||||
Runtime statistics component for ESPHome.
|
||||
"""
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
|
||||
DEPENDENCIES = []
|
||||
|
||||
CONF_ENABLED = "enabled"
|
||||
CONF_LOG_INTERVAL = "log_interval"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ENABLED, default=True): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_LOG_INTERVAL, default=60000
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
"""Generate code for the runtime statistics component."""
|
||||
cg.add(cg.App.set_runtime_stats_enabled(config[CONF_ENABLED]))
|
||||
cg.add(cg.App.set_runtime_stats_log_interval(config[CONF_LOG_INTERVAL]))
|
||||
@@ -136,10 +136,6 @@ void Application::loop() {
|
||||
this->in_loop_ = false;
|
||||
this->app_state_ = new_app_state;
|
||||
|
||||
// Process any pending runtime stats printing after all components have run
|
||||
// This ensures stats printing doesn't affect component timing measurements
|
||||
runtime_stats.process_pending_stats(last_op_end_time);
|
||||
|
||||
// Use the last component's end time instead of calling millis() again
|
||||
auto elapsed = last_op_end_time - this->last_loop_;
|
||||
if (elapsed >= this->loop_interval_ || HighFrequencyLoopRequester::is_high_frequency()) {
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/runtime_stats.h"
|
||||
#include "esphome/core/scheduler.h"
|
||||
|
||||
#ifdef USE_DEVICES
|
||||
@@ -338,6 +337,9 @@ class Application {
|
||||
* Each component can request a high frequency loop execution by using the HighFrequencyLoopRequester
|
||||
* helper in helpers.h
|
||||
*
|
||||
* Note: This method is not called by ESPHome core code. It is only used by lambda functions
|
||||
* in YAML configurations or by external components.
|
||||
*
|
||||
* @param loop_interval The interval in milliseconds to run the core loop at. Defaults to 16 milliseconds.
|
||||
*/
|
||||
void set_loop_interval(uint32_t loop_interval) {
|
||||
@@ -346,18 +348,6 @@ class Application {
|
||||
|
||||
uint32_t get_loop_interval() const { return static_cast<uint32_t>(this->loop_interval_); }
|
||||
|
||||
/** Enable or disable runtime statistics collection.
|
||||
*
|
||||
* @param enable Whether to enable runtime statistics collection.
|
||||
*/
|
||||
void set_runtime_stats_enabled(bool enable) { runtime_stats.set_enabled(enable); }
|
||||
|
||||
/** Set the interval at which runtime statistics are logged.
|
||||
*
|
||||
* @param interval The interval in milliseconds between logging of runtime statistics.
|
||||
*/
|
||||
void set_runtime_stats_log_interval(uint32_t interval) { runtime_stats.set_log_interval(interval); }
|
||||
|
||||
void schedule_dump_config() { this->dump_config_at_ = 0; }
|
||||
|
||||
void feed_wdt(uint32_t time = 0);
|
||||
|
||||
@@ -303,9 +303,6 @@ uint32_t WarnIfComponentBlockingGuard::finish() {
|
||||
uint32_t curr_time = millis();
|
||||
|
||||
uint32_t blocking_time = curr_time - this->started_;
|
||||
|
||||
// Record component runtime stats
|
||||
runtime_stats.record_component_time(this->component_, blocking_time, curr_time);
|
||||
bool should_warn;
|
||||
if (this->component_ != nullptr) {
|
||||
should_warn = this->component_->should_warn_of_blocking(blocking_time);
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <string>
|
||||
|
||||
#include "esphome/core/optional.h"
|
||||
#include "esphome/core/runtime_stats.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
|
||||
@@ -375,7 +375,7 @@ void ComponentIterator::advance() {
|
||||
}
|
||||
|
||||
if (advance_platform) {
|
||||
this->state_ = static_cast<IteratorState>(static_cast<uint16_t>(this->state_) + 1);
|
||||
this->state_ = static_cast<IteratorState>(static_cast<uint32_t>(this->state_) + 1);
|
||||
this->at_ = 0;
|
||||
} else if (success) {
|
||||
this->at_++;
|
||||
|
||||
@@ -93,9 +93,7 @@ class ComponentIterator {
|
||||
virtual bool on_end();
|
||||
|
||||
protected:
|
||||
// Iterates over all ESPHome entities (sensors, switches, lights, etc.)
|
||||
// Supports up to 256 entity types and up to 65,535 entities of each type
|
||||
enum class IteratorState : uint8_t {
|
||||
enum class IteratorState {
|
||||
NONE = 0,
|
||||
BEGIN,
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
@@ -169,7 +167,7 @@ class ComponentIterator {
|
||||
#endif
|
||||
MAX,
|
||||
} state_{IteratorState::NONE};
|
||||
uint16_t at_{0}; // Supports up to 65,535 entities per type
|
||||
size_t at_{0};
|
||||
bool include_internal_{false};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
#include "esphome/core/runtime_stats.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
RuntimeStatsCollector runtime_stats;
|
||||
|
||||
void RuntimeStatsCollector::record_component_time(Component *component, uint32_t duration_ms, uint32_t current_time) {
|
||||
if (!this->enabled_ || component == nullptr)
|
||||
return;
|
||||
|
||||
// Check if we have cached the name for this component
|
||||
auto name_it = this->component_names_cache_.find(component);
|
||||
if (name_it == this->component_names_cache_.end()) {
|
||||
// First time seeing this component, cache its name
|
||||
const char *source = component->get_component_source();
|
||||
this->component_names_cache_[component] = source;
|
||||
this->component_stats_[source].record_time(duration_ms);
|
||||
} else {
|
||||
// Use cached name - no string operations, just map lookup
|
||||
this->component_stats_[name_it->second].record_time(duration_ms);
|
||||
}
|
||||
|
||||
// If next_log_time_ is 0, initialize it
|
||||
if (this->next_log_time_ == 0) {
|
||||
this->next_log_time_ = current_time + this->log_interval_;
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't print stats here anymore - let process_pending_stats handle it
|
||||
}
|
||||
|
||||
void RuntimeStatsCollector::log_stats_() {
|
||||
ESP_LOGI(RUNTIME_TAG, "Component Runtime Statistics");
|
||||
ESP_LOGI(RUNTIME_TAG, "Period stats (last %" PRIu32 "ms):", this->log_interval_);
|
||||
|
||||
// First collect stats we want to display
|
||||
std::vector<ComponentStatPair> stats_to_display;
|
||||
|
||||
for (const auto &it : this->component_stats_) {
|
||||
const ComponentRuntimeStats &stats = it.second;
|
||||
if (stats.get_period_count() > 0) {
|
||||
ComponentStatPair pair = {it.first, &stats};
|
||||
stats_to_display.push_back(pair);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by period runtime (descending)
|
||||
std::sort(stats_to_display.begin(), stats_to_display.end(), std::greater<ComponentStatPair>());
|
||||
|
||||
// Log top components by period runtime
|
||||
for (const auto &it : stats_to_display) {
|
||||
const std::string &source = it.name;
|
||||
const ComponentRuntimeStats *stats = it.stats;
|
||||
|
||||
ESP_LOGI(RUNTIME_TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms", source.c_str(),
|
||||
stats->get_period_count(), stats->get_period_avg_time_ms(), stats->get_period_max_time_ms(),
|
||||
stats->get_period_time_ms());
|
||||
}
|
||||
|
||||
// Log total stats since boot
|
||||
ESP_LOGI(RUNTIME_TAG, "Total stats (since boot):");
|
||||
|
||||
// Re-sort by total runtime for all-time stats
|
||||
std::sort(stats_to_display.begin(), stats_to_display.end(),
|
||||
[](const ComponentStatPair &a, const ComponentStatPair &b) {
|
||||
return a.stats->get_total_time_ms() > b.stats->get_total_time_ms();
|
||||
});
|
||||
|
||||
for (const auto &it : stats_to_display) {
|
||||
const std::string &source = it.name;
|
||||
const ComponentRuntimeStats *stats = it.stats;
|
||||
|
||||
ESP_LOGI(RUNTIME_TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms", source.c_str(),
|
||||
stats->get_total_count(), stats->get_total_avg_time_ms(), stats->get_total_max_time_ms(),
|
||||
stats->get_total_time_ms());
|
||||
}
|
||||
}
|
||||
|
||||
void RuntimeStatsCollector::process_pending_stats(uint32_t current_time) {
|
||||
if (!this->enabled_ || this->next_log_time_ == 0)
|
||||
return;
|
||||
|
||||
if (current_time >= this->next_log_time_) {
|
||||
this->log_stats_();
|
||||
this->reset_stats_();
|
||||
this->next_log_time_ = current_time + this->log_interval_;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
@@ -1,121 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
static const char *const RUNTIME_TAG = "runtime";
|
||||
|
||||
class Component; // Forward declaration
|
||||
|
||||
class ComponentRuntimeStats {
|
||||
public:
|
||||
ComponentRuntimeStats()
|
||||
: period_count_(0),
|
||||
total_count_(0),
|
||||
period_time_ms_(0),
|
||||
total_time_ms_(0),
|
||||
period_max_time_ms_(0),
|
||||
total_max_time_ms_(0) {}
|
||||
|
||||
void record_time(uint32_t duration_ms) {
|
||||
// Update period counters
|
||||
this->period_count_++;
|
||||
this->period_time_ms_ += duration_ms;
|
||||
if (duration_ms > this->period_max_time_ms_)
|
||||
this->period_max_time_ms_ = duration_ms;
|
||||
|
||||
// Update total counters
|
||||
this->total_count_++;
|
||||
this->total_time_ms_ += duration_ms;
|
||||
if (duration_ms > this->total_max_time_ms_)
|
||||
this->total_max_time_ms_ = duration_ms;
|
||||
}
|
||||
|
||||
void reset_period_stats() {
|
||||
this->period_count_ = 0;
|
||||
this->period_time_ms_ = 0;
|
||||
this->period_max_time_ms_ = 0;
|
||||
}
|
||||
|
||||
// Period stats (reset each logging interval)
|
||||
uint32_t get_period_count() const { return this->period_count_; }
|
||||
uint32_t get_period_time_ms() const { return this->period_time_ms_; }
|
||||
uint32_t get_period_max_time_ms() const { return this->period_max_time_ms_; }
|
||||
float get_period_avg_time_ms() const {
|
||||
return this->period_count_ > 0 ? this->period_time_ms_ / static_cast<float>(this->period_count_) : 0.0f;
|
||||
}
|
||||
|
||||
// Total stats (persistent until reboot)
|
||||
uint32_t get_total_count() const { return this->total_count_; }
|
||||
uint32_t get_total_time_ms() const { return this->total_time_ms_; }
|
||||
uint32_t get_total_max_time_ms() const { return this->total_max_time_ms_; }
|
||||
float get_total_avg_time_ms() const {
|
||||
return this->total_count_ > 0 ? this->total_time_ms_ / static_cast<float>(this->total_count_) : 0.0f;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Period stats (reset each logging interval)
|
||||
uint32_t period_count_;
|
||||
uint32_t period_time_ms_;
|
||||
uint32_t period_max_time_ms_;
|
||||
|
||||
// Total stats (persistent until reboot)
|
||||
uint32_t total_count_;
|
||||
uint32_t total_time_ms_;
|
||||
uint32_t total_max_time_ms_;
|
||||
};
|
||||
|
||||
// For sorting components by run time
|
||||
struct ComponentStatPair {
|
||||
std::string name;
|
||||
const ComponentRuntimeStats *stats;
|
||||
|
||||
bool operator>(const ComponentStatPair &other) const {
|
||||
// Sort by period time as that's what we're displaying in the logs
|
||||
return stats->get_period_time_ms() > other.stats->get_period_time_ms();
|
||||
}
|
||||
};
|
||||
|
||||
class RuntimeStatsCollector {
|
||||
public:
|
||||
RuntimeStatsCollector() : log_interval_(60000), next_log_time_(0), enabled_(true) {}
|
||||
|
||||
void set_log_interval(uint32_t log_interval) { this->log_interval_ = log_interval; }
|
||||
uint32_t get_log_interval() const { return this->log_interval_; }
|
||||
|
||||
void set_enabled(bool enabled) { this->enabled_ = enabled; }
|
||||
bool is_enabled() const { return this->enabled_; }
|
||||
|
||||
void record_component_time(Component *component, uint32_t duration_ms, uint32_t current_time);
|
||||
|
||||
// Process any pending stats printing (should be called after component loop)
|
||||
void process_pending_stats(uint32_t current_time);
|
||||
|
||||
protected:
|
||||
void log_stats_();
|
||||
|
||||
void reset_stats_() {
|
||||
for (auto &it : this->component_stats_) {
|
||||
it.second.reset_period_stats();
|
||||
}
|
||||
}
|
||||
|
||||
// Back to string keys, but we'll cache the source name per component
|
||||
std::map<std::string, ComponentRuntimeStats> component_stats_;
|
||||
std::map<Component *, std::string> component_names_cache_;
|
||||
uint32_t log_interval_;
|
||||
uint32_t next_log_time_;
|
||||
bool enabled_;
|
||||
};
|
||||
|
||||
// Global instance for runtime stats collection
|
||||
extern RuntimeStatsCollector runtime_stats;
|
||||
|
||||
} // namespace esphome
|
||||
@@ -886,7 +886,7 @@ def build_message_type(
|
||||
public_content.append("#ifdef HAS_PROTO_MESSAGE_DUMP")
|
||||
snake_name = camel_to_snake(desc.name)
|
||||
public_content.append(
|
||||
f'static constexpr const char *message_name() {{ return "{snake_name}"; }}'
|
||||
f'const char *message_name() const override {{ return "{snake_name}"; }}'
|
||||
)
|
||||
public_content.append("#endif")
|
||||
|
||||
@@ -1356,7 +1356,7 @@ def main() -> None:
|
||||
hpp += " template<typename T>\n"
|
||||
hpp += " bool send_message(const T &msg) {\n"
|
||||
hpp += "#ifdef HAS_PROTO_MESSAGE_DUMP\n"
|
||||
hpp += " this->log_send_message_(T::message_name(), msg.dump());\n"
|
||||
hpp += " this->log_send_message_(msg.message_name(), msg.dump());\n"
|
||||
hpp += "#endif\n"
|
||||
hpp += " return this->send_message_(msg, T::MESSAGE_TYPE);\n"
|
||||
hpp += " }\n\n"
|
||||
|
||||
0
script/run-in-env.py
Normal file → Executable file
0
script/run-in-env.py
Normal file → Executable file
Reference in New Issue
Block a user