mirror of
https://github.com/esphome/esphome.git
synced 2026-01-19 17:46:23 -07:00
Compare commits
6 Commits
reboot_tim
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4f265796c | ||
|
|
2c16d6eb9a | ||
|
|
f32ecb73db | ||
|
|
484087f780 | ||
|
|
d91eed1411 | ||
|
|
47714a1745 |
@@ -169,7 +169,8 @@ void APIConnection::loop() {
|
||||
} else {
|
||||
this->last_traffic_ = now;
|
||||
// read a packet
|
||||
this->read_message(buffer.data_len, buffer.type, buffer.data);
|
||||
this->read_message(buffer.data_len, buffer.type,
|
||||
buffer.data_len > 0 ? &buffer.container[buffer.data_offset] : nullptr);
|
||||
if (this->flags_.remove)
|
||||
return;
|
||||
}
|
||||
@@ -194,9 +195,6 @@ void APIConnection::loop() {
|
||||
}
|
||||
// Now that everything is sent, enable immediate sending for future state changes
|
||||
this->flags_.should_try_send_immediately = true;
|
||||
// Release excess memory from buffers that grew during initial sync
|
||||
this->deferred_batch_.release_buffer();
|
||||
this->helper_->release_buffers();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -554,8 +554,10 @@ class APIConnection final : public APIServerConnection {
|
||||
std::vector<BatchItem> items;
|
||||
uint32_t batch_start_time{0};
|
||||
|
||||
// No pre-allocation - log connections never use batching, and for
|
||||
// connections that do, buffers are released after initial sync anyway
|
||||
DeferredBatch() {
|
||||
// Pre-allocate capacity for typical batch sizes to avoid reallocation
|
||||
items.reserve(8);
|
||||
}
|
||||
|
||||
// Add item to the batch
|
||||
void add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
|
||||
@@ -574,15 +576,6 @@ class APIConnection final : public APIServerConnection {
|
||||
bool empty() const { return items.empty(); }
|
||||
size_t size() const { return items.size(); }
|
||||
const BatchItem &operator[](size_t index) const { return items[index]; }
|
||||
// Release excess capacity - only releases if items already empty
|
||||
void release_buffer() {
|
||||
// Safe to call: batch is processed before release_buffer is called,
|
||||
// and if any items remain (partial processing), we must not clear them.
|
||||
// Use swap trick since shrink_to_fit() is non-binding and may be ignored.
|
||||
if (items.empty()) {
|
||||
std::vector<BatchItem>().swap(items);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// DeferredBatch here (16 bytes, 4-byte aligned)
|
||||
|
||||
@@ -35,9 +35,10 @@ struct ClientInfo;
|
||||
class ProtoWriteBuffer;
|
||||
|
||||
struct ReadPacketBuffer {
|
||||
const uint8_t *data; // Points directly into frame helper's rx_buf_ (valid until next read_packet call)
|
||||
uint16_t data_len;
|
||||
std::vector<uint8_t> container;
|
||||
uint16_t type;
|
||||
uint16_t data_offset;
|
||||
uint16_t data_len;
|
||||
};
|
||||
|
||||
// Packed packet info structure to minimize memory usage
|
||||
@@ -118,22 +119,6 @@ class APIFrameHelper {
|
||||
uint8_t frame_footer_size() const { return frame_footer_size_; }
|
||||
// Check if socket has data ready to read
|
||||
bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
|
||||
// Release excess memory from internal buffers after initial sync
|
||||
void release_buffers() {
|
||||
// rx_buf_: Safe to clear only if no partial read in progress.
|
||||
// rx_buf_len_ tracks bytes read so far; if non-zero, we're mid-frame
|
||||
// and clearing would lose partially received data.
|
||||
if (this->rx_buf_len_ == 0) {
|
||||
// Use swap trick since shrink_to_fit() is non-binding and may be ignored
|
||||
std::vector<uint8_t>().swap(this->rx_buf_);
|
||||
}
|
||||
// reusable_iovs_: Safe to release unconditionally.
|
||||
// Only used within write_protobuf_packets() calls - cleared at start,
|
||||
// populated with pointers, used for writev(), then function returns.
|
||||
// The iovecs contain stale pointers after the call (data was either sent
|
||||
// or copied to tx_buf_), and are cleared on next write_protobuf_packets().
|
||||
std::vector<struct iovec>().swap(this->reusable_iovs_);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Buffer containing data to be sent
|
||||
|
||||
@@ -407,7 +407,8 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
return APIError::BAD_DATA_PACKET;
|
||||
}
|
||||
|
||||
buffer->data = msg_data + 4; // Skip 4-byte header (type + length)
|
||||
buffer->container = std::move(this->rx_buf_);
|
||||
buffer->data_offset = 4;
|
||||
buffer->data_len = data_len;
|
||||
buffer->type = type;
|
||||
return APIError::OK;
|
||||
|
||||
@@ -210,7 +210,8 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
return aerr;
|
||||
}
|
||||
|
||||
buffer->data = this->rx_buf_.data();
|
||||
buffer->container = std::move(this->rx_buf_);
|
||||
buffer->data_offset = 0;
|
||||
buffer->data_len = this->rx_header_parsed_len_;
|
||||
buffer->type = this->rx_header_parsed_type_;
|
||||
return APIError::OK;
|
||||
|
||||
@@ -13,7 +13,7 @@ void APIServerConnectionBase::log_send_message_(const char *name, const std::str
|
||||
}
|
||||
#endif
|
||||
|
||||
void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
|
||||
void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
|
||||
switch (msg_type) {
|
||||
case HelloRequest::MESSAGE_TYPE: {
|
||||
HelloRequest msg;
|
||||
@@ -827,7 +827,7 @@ void APIServerConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) { th
|
||||
void APIServerConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) { this->zwave_proxy_request(msg); }
|
||||
#endif
|
||||
|
||||
void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
|
||||
void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
|
||||
// Check authentication/connection requirements for messages
|
||||
switch (msg_type) {
|
||||
case HelloRequest::MESSAGE_TYPE: // No setup required
|
||||
|
||||
@@ -218,7 +218,7 @@ class APIServerConnectionBase : public ProtoService {
|
||||
virtual void on_z_wave_proxy_request(const ZWaveProxyRequest &value){};
|
||||
#endif
|
||||
protected:
|
||||
void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;
|
||||
void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
||||
};
|
||||
|
||||
class APIServerConnection : public APIServerConnectionBase {
|
||||
@@ -480,7 +480,7 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override;
|
||||
#endif
|
||||
void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;
|
||||
void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
||||
};
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
@@ -101,7 +101,19 @@ void APIServer::setup() {
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
if (logger::global_logger != nullptr) {
|
||||
logger::global_logger->add_log_listener(this);
|
||||
logger::global_logger->add_on_log_callback(
|
||||
[this](int level, const char *tag, const char *message, size_t message_len) {
|
||||
if (this->shutting_down_) {
|
||||
// Don't try to send logs during shutdown
|
||||
// as it could result in a recursion and
|
||||
// we would be filling a buffer we are trying to clear
|
||||
return;
|
||||
}
|
||||
for (auto &c : this->clients_) {
|
||||
if (!c->flags_.remove && c->get_log_subscription_level() >= level)
|
||||
c->try_send_log_message(level, tag, message, message_len);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -117,11 +129,9 @@ void APIServer::setup() {
|
||||
#endif
|
||||
}
|
||||
|
||||
static const char *const REBOOT_TIMEOUT = "reboot";
|
||||
|
||||
void APIServer::schedule_reboot_timeout_() {
|
||||
this->status_set_warning();
|
||||
this->set_timeout(REBOOT_TIMEOUT, this->reboot_timeout_, []() {
|
||||
this->set_timeout("api_reboot", this->reboot_timeout_, []() {
|
||||
if (!global_api_server->is_connected()) {
|
||||
ESP_LOGE(TAG, "No clients; rebooting");
|
||||
App.reboot();
|
||||
@@ -157,7 +167,7 @@ void APIServer::loop() {
|
||||
// Clear warning status and cancel reboot when first client connects
|
||||
if (this->clients_.size() == 1 && this->reboot_timeout_ != 0) {
|
||||
this->status_clear_warning();
|
||||
this->cancel_timeout(REBOOT_TIMEOUT);
|
||||
this->cancel_timeout("api_reboot");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -531,21 +541,6 @@ bool APIServer::is_connected(bool state_subscription_only) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
void APIServer::on_log(uint8_t level, const char *tag, const char *message, size_t message_len) {
|
||||
if (this->shutting_down_) {
|
||||
// Don't try to send logs during shutdown
|
||||
// as it could result in a recursion and
|
||||
// we would be filling a buffer we are trying to clear
|
||||
return;
|
||||
}
|
||||
for (auto &c : this->clients_) {
|
||||
if (!c->flags_.remove && c->get_log_subscription_level() >= level)
|
||||
c->try_send_log_message(level, tag, message, message_len);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void APIServer::on_shutdown() {
|
||||
this->shutting_down_ = true;
|
||||
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
#ifdef USE_API_USER_DEFINED_ACTIONS
|
||||
#include "user_services.h"
|
||||
#endif
|
||||
#ifdef USE_LOGGER
|
||||
#include "esphome/components/logger/logger.h"
|
||||
#endif
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
@@ -30,13 +27,7 @@ struct SavedNoisePsk {
|
||||
} PACKED; // NOLINT
|
||||
#endif
|
||||
|
||||
class APIServer : public Component,
|
||||
public Controller
|
||||
#ifdef USE_LOGGER
|
||||
,
|
||||
public logger::LogListener
|
||||
#endif
|
||||
{
|
||||
class APIServer : public Component, public Controller {
|
||||
public:
|
||||
APIServer();
|
||||
void setup() override;
|
||||
@@ -46,9 +37,6 @@ class APIServer : public Component,
|
||||
void dump_config() override;
|
||||
void on_shutdown() override;
|
||||
bool teardown() override;
|
||||
#ifdef USE_LOGGER
|
||||
void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override;
|
||||
#endif
|
||||
#ifdef USE_API_PASSWORD
|
||||
bool check_password(const uint8_t *password_data, size_t password_len) const;
|
||||
void set_password(const std::string &password);
|
||||
|
||||
@@ -846,7 +846,7 @@ class ProtoService {
|
||||
*/
|
||||
virtual ProtoWriteBuffer create_buffer(uint32_t reserve_size) = 0;
|
||||
virtual bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) = 0;
|
||||
virtual void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) = 0;
|
||||
virtual void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0;
|
||||
|
||||
// Optimized method that pre-allocates buffer based on message size
|
||||
bool send_message_(const ProtoMessage &msg, uint8_t message_type) {
|
||||
|
||||
@@ -87,20 +87,16 @@ void BLENUS::setup() {
|
||||
global_ble_nus = this;
|
||||
#ifdef USE_LOGGER
|
||||
if (logger::global_logger != nullptr && this->expose_log_) {
|
||||
logger::global_logger->add_log_listener(this);
|
||||
logger::global_logger->add_on_log_callback(
|
||||
[this](int level, const char *tag, const char *message, size_t message_len) {
|
||||
this->write_array(reinterpret_cast<const uint8_t *>(message), message_len);
|
||||
const char c = '\n';
|
||||
this->write_array(reinterpret_cast<const uint8_t *>(&c), 1);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
void BLENUS::on_log(uint8_t level, const char *tag, const char *message, size_t message_len) {
|
||||
(void) level;
|
||||
(void) tag;
|
||||
this->write_array(reinterpret_cast<const uint8_t *>(message), message_len);
|
||||
const char c = '\n';
|
||||
this->write_array(reinterpret_cast<const uint8_t *>(&c), 1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void BLENUS::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ble nus:");
|
||||
|
||||
@@ -2,20 +2,12 @@
|
||||
#ifdef USE_ZEPHYR
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/component.h"
|
||||
#ifdef USE_LOGGER
|
||||
#include "esphome/components/logger/logger.h"
|
||||
#endif
|
||||
#include <shell/shell_bt_nus.h>
|
||||
#include <atomic>
|
||||
|
||||
namespace esphome::ble_nus {
|
||||
|
||||
class BLENUS : public Component
|
||||
#ifdef USE_LOGGER
|
||||
,
|
||||
public logger::LogListener
|
||||
#endif
|
||||
{
|
||||
class BLENUS : public Component {
|
||||
enum TxStatus {
|
||||
TX_DISABLED,
|
||||
TX_ENABLED,
|
||||
@@ -28,9 +20,6 @@ class BLENUS : public Component
|
||||
void loop() override;
|
||||
size_t write_array(const uint8_t *data, size_t len);
|
||||
void set_expose_log(bool expose_log) { this->expose_log_ = expose_log; }
|
||||
#ifdef USE_LOGGER
|
||||
void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
static void send_enabled_callback(bt_nus_send_status status);
|
||||
|
||||
@@ -27,13 +27,11 @@ void BluetoothProxy::setup() {
|
||||
// Capture the configured scan mode from YAML before any API changes
|
||||
this->configured_scan_active_ = this->parent_->get_scan_active();
|
||||
|
||||
this->parent_->add_scanner_state_listener(this);
|
||||
}
|
||||
|
||||
void BluetoothProxy::on_scanner_state(esp32_ble_tracker::ScannerState state) {
|
||||
if (this->api_connection_ != nullptr) {
|
||||
this->send_bluetooth_scanner_state_(state);
|
||||
}
|
||||
this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
|
||||
if (this->api_connection_ != nullptr) {
|
||||
this->send_bluetooth_scanner_state_(state);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state) {
|
||||
|
||||
@@ -52,9 +52,7 @@ enum BluetoothProxySubscriptionFlag : uint32_t {
|
||||
SUBSCRIPTION_RAW_ADVERTISEMENTS = 1 << 0,
|
||||
};
|
||||
|
||||
class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener,
|
||||
public esp32_ble_tracker::BLEScannerStateListener,
|
||||
public Component {
|
||||
class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||
friend class BluetoothConnection; // Allow connection to update connections_free_response_
|
||||
public:
|
||||
BluetoothProxy();
|
||||
@@ -110,9 +108,6 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener,
|
||||
void set_active(bool active) { this->active_ = active; }
|
||||
bool has_active() { return this->active_; }
|
||||
|
||||
/// BLEScannerStateListener interface
|
||||
void on_scanner_state(esp32_ble_tracker::ScannerState state) override;
|
||||
|
||||
uint32_t get_legacy_version() const {
|
||||
if (this->active_) {
|
||||
return LEGACY_ACTIVE_CONNECTIONS_VERSION;
|
||||
|
||||
@@ -37,7 +37,6 @@ from esphome.const import (
|
||||
__version__,
|
||||
)
|
||||
from esphome.core import CORE, HexInt, TimePeriod
|
||||
from esphome.coroutine import CoroPriority, coroutine_with_priority
|
||||
import esphome.final_validate as fv
|
||||
from esphome.helpers import copy_file_if_changed, write_file_if_changed
|
||||
from esphome.types import ConfigType
|
||||
@@ -263,32 +262,15 @@ def add_idf_component(
|
||||
"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."
|
||||
)
|
||||
components_registry = CORE.data[KEY_ESP32][KEY_COMPONENTS]
|
||||
if components:
|
||||
for comp in components:
|
||||
existing = components_registry.get(comp)
|
||||
if existing and existing.get(KEY_REF) != ref:
|
||||
_LOGGER.warning(
|
||||
"IDF component %s version conflict %s replaced by %s",
|
||||
comp,
|
||||
existing.get(KEY_REF),
|
||||
ref,
|
||||
)
|
||||
components_registry[comp] = {
|
||||
CORE.data[KEY_ESP32][KEY_COMPONENTS][comp] = {
|
||||
KEY_REPO: repo,
|
||||
KEY_REF: ref,
|
||||
KEY_PATH: f"{path}/{comp}" if path else comp,
|
||||
}
|
||||
else:
|
||||
existing = components_registry.get(name)
|
||||
if existing and existing.get(KEY_REF) != ref:
|
||||
_LOGGER.warning(
|
||||
"IDF component %s version conflict %s replaced by %s",
|
||||
name,
|
||||
existing.get(KEY_REF),
|
||||
ref,
|
||||
)
|
||||
components_registry[name] = {
|
||||
CORE.data[KEY_ESP32][KEY_COMPONENTS][name] = {
|
||||
KEY_REPO: repo,
|
||||
KEY_REF: ref,
|
||||
KEY_PATH: path,
|
||||
@@ -610,14 +592,6 @@ def require_vfs_dir() -> None:
|
||||
CORE.data[KEY_VFS_DIR_REQUIRED] = True
|
||||
|
||||
|
||||
def _parse_idf_component(value: str) -> ConfigType:
|
||||
"""Parse IDF component shorthand syntax like 'owner/component^version'"""
|
||||
if "^" not in value:
|
||||
raise cv.Invalid(f"Invalid IDF component shorthand '{value}'")
|
||||
name, ref = value.split("^", 1)
|
||||
return {CONF_NAME: name, CONF_REF: ref}
|
||||
|
||||
|
||||
def _validate_idf_component(config: ConfigType) -> ConfigType:
|
||||
"""Validate IDF component config and warn about deprecated options."""
|
||||
if CONF_REFRESH in config:
|
||||
@@ -685,19 +659,14 @@ FRAMEWORK_SCHEMA = cv.Schema(
|
||||
),
|
||||
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
|
||||
cv.All(
|
||||
cv.Any(
|
||||
cv.All(cv.string_strict, _parse_idf_component),
|
||||
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
|
||||
),
|
||||
}
|
||||
),
|
||||
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,
|
||||
)
|
||||
@@ -882,18 +851,6 @@ def _configure_lwip_max_sockets(conf: dict) -> None:
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_MAX_SOCKETS", max_sockets)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.FINAL)
|
||||
async def _add_yaml_idf_components(components: list[ConfigType]):
|
||||
"""Add IDF components from YAML config with final priority to override code-added components."""
|
||||
for component in components:
|
||||
add_idf_component(
|
||||
name=component[CONF_NAME],
|
||||
repo=component.get(CONF_SOURCE),
|
||||
ref=component.get(CONF_REF),
|
||||
path=component.get(CONF_PATH),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
cg.add_platformio_option("board", config[CONF_BOARD])
|
||||
cg.add_platformio_option("board_upload.flash_size", config[CONF_FLASH_SIZE])
|
||||
@@ -1140,10 +1097,13 @@ async def to_code(config):
|
||||
for name, value in conf[CONF_SDKCONFIG_OPTIONS].items():
|
||||
add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
|
||||
|
||||
# Components from YAML are added in a separate coroutine with FINAL priority
|
||||
# Schedule it to run after all other components
|
||||
if conf[CONF_COMPONENTS]:
|
||||
CORE.add_job(_add_yaml_idf_components, conf[CONF_COMPONENTS])
|
||||
for component in conf[CONF_COMPONENTS]:
|
||||
add_idf_component(
|
||||
name=component[CONF_NAME],
|
||||
repo=component.get(CONF_SOURCE),
|
||||
ref=component.get(CONF_REF),
|
||||
path=component.get(CONF_PATH),
|
||||
)
|
||||
|
||||
|
||||
APP_PARTITION_SIZES = {
|
||||
|
||||
@@ -373,9 +373,7 @@ void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
|
||||
|
||||
void ESP32BLETracker::set_scanner_state_(ScannerState state) {
|
||||
this->scanner_state_ = state;
|
||||
for (auto *listener : this->scanner_state_listeners_) {
|
||||
listener->on_scanner_state(state);
|
||||
}
|
||||
this->scanner_state_callbacks_.call(state);
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
|
||||
@@ -180,16 +180,6 @@ enum class ScannerState {
|
||||
STOPPING,
|
||||
};
|
||||
|
||||
/** Listener interface for BLE scanner state changes.
|
||||
*
|
||||
* Components can implement this interface to receive scanner state updates
|
||||
* without the overhead of std::function callbacks.
|
||||
*/
|
||||
class BLEScannerStateListener {
|
||||
public:
|
||||
virtual void on_scanner_state(ScannerState state) = 0;
|
||||
};
|
||||
|
||||
// Helper function to convert ClientState to string
|
||||
const char *client_state_to_string(ClientState state);
|
||||
|
||||
@@ -274,9 +264,8 @@ class ESP32BLETracker : public Component,
|
||||
void gap_scan_event_handler(const BLEScanResult &scan_result) override;
|
||||
void ble_before_disabled_event_handler() override;
|
||||
|
||||
/// Add a listener for scanner state changes
|
||||
void add_scanner_state_listener(BLEScannerStateListener *listener) {
|
||||
this->scanner_state_listeners_.push_back(listener);
|
||||
void add_scanner_state_callback(std::function<void(ScannerState)> &&callback) {
|
||||
this->scanner_state_callbacks_.add(std::move(callback));
|
||||
}
|
||||
ScannerState get_scanner_state() const { return this->scanner_state_; }
|
||||
|
||||
@@ -333,14 +322,14 @@ class ESP32BLETracker : public Component,
|
||||
return counts;
|
||||
}
|
||||
|
||||
// Group 1: Large objects (12+ bytes) - vectors
|
||||
// Group 1: Large objects (12+ bytes) - vectors and callback manager
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT
|
||||
StaticVector<ESPBTDeviceListener *, ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT> listeners_;
|
||||
#endif
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
|
||||
StaticVector<ESPBTClient *, ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT> clients_;
|
||||
#endif
|
||||
std::vector<BLEScannerStateListener *> scanner_state_listeners_;
|
||||
CallbackManager<void(ScannerState)> scanner_state_callbacks_;
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
/// Vector of addresses that have already been printed in print_bt_device_info
|
||||
std::vector<uint64_t> already_discovered_;
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
#include <esp_event.h>
|
||||
#include <esp_mac.h>
|
||||
#include <esp_netif.h>
|
||||
#include <esp_now.h>
|
||||
#include <esp_random.h>
|
||||
#include <esp_wifi.h>
|
||||
@@ -158,12 +157,6 @@ bool ESPNowComponent::is_wifi_enabled() {
|
||||
}
|
||||
|
||||
void ESPNowComponent::setup() {
|
||||
#ifndef USE_WIFI
|
||||
// Initialize LwIP stack for wake_loop_threadsafe() socket support
|
||||
// When WiFi component is present, it handles esp_netif_init()
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
#endif
|
||||
|
||||
if (this->enable_on_boot_) {
|
||||
this->enable_();
|
||||
} else {
|
||||
|
||||
@@ -7,29 +7,30 @@ namespace esphome::light {
|
||||
|
||||
// See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema
|
||||
|
||||
// Get JSON string for color mode using linear search (avoids large switch jump table)
|
||||
static const char *get_color_mode_json_str(ColorMode mode) {
|
||||
// Parallel arrays: mode values and their corresponding strings
|
||||
// Uses less RAM than a switch jump table on sparse enum values
|
||||
static constexpr ColorMode MODES[] = {
|
||||
ColorMode::ON_OFF,
|
||||
ColorMode::BRIGHTNESS,
|
||||
ColorMode::WHITE,
|
||||
ColorMode::COLOR_TEMPERATURE,
|
||||
ColorMode::COLD_WARM_WHITE,
|
||||
ColorMode::RGB,
|
||||
ColorMode::RGB_WHITE,
|
||||
ColorMode::RGB_COLOR_TEMPERATURE,
|
||||
ColorMode::RGB_COLD_WARM_WHITE,
|
||||
};
|
||||
static constexpr const char *STRINGS[] = {
|
||||
"onoff", "brightness", "white", "color_temp", "cwww", "rgb", "rgbw", "rgbct", "rgbww",
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(MODES) / sizeof(MODES[0]); i++) {
|
||||
if (MODES[i] == mode)
|
||||
return STRINGS[i];
|
||||
// Lookup table for color mode strings
|
||||
static constexpr const char *get_color_mode_json_str(ColorMode mode) {
|
||||
switch (mode) {
|
||||
case ColorMode::ON_OFF:
|
||||
return "onoff";
|
||||
case ColorMode::BRIGHTNESS:
|
||||
return "brightness";
|
||||
case ColorMode::WHITE:
|
||||
return "white"; // not supported by HA in MQTT
|
||||
case ColorMode::COLOR_TEMPERATURE:
|
||||
return "color_temp";
|
||||
case ColorMode::COLD_WARM_WHITE:
|
||||
return "cwww"; // not supported by HA
|
||||
case ColorMode::RGB:
|
||||
return "rgb";
|
||||
case ColorMode::RGB_WHITE:
|
||||
return "rgbw";
|
||||
case ColorMode::RGB_COLOR_TEMPERATURE:
|
||||
return "rgbct"; // not supported by HA
|
||||
case ColorMode::RGB_COLD_WARM_WHITE:
|
||||
return "rgbww";
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
|
||||
|
||||
@@ -406,8 +406,6 @@ async def to_code(config):
|
||||
conf,
|
||||
)
|
||||
|
||||
CORE.add_job(final_step)
|
||||
|
||||
|
||||
def validate_printf(value):
|
||||
# https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python
|
||||
@@ -508,24 +506,3 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
# Keys for CORE.data storage
|
||||
DOMAIN = "logger"
|
||||
KEY_LEVEL_LISTENERS = "level_listeners"
|
||||
|
||||
|
||||
def request_logger_level_listeners() -> None:
|
||||
"""Request that logger level listeners be compiled in.
|
||||
|
||||
Components that need to be notified about log level changes should call this
|
||||
function during their code generation. This enables the add_level_listener()
|
||||
method and compiles in the listener vector.
|
||||
"""
|
||||
CORE.data.setdefault(DOMAIN, {})[KEY_LEVEL_LISTENERS] = True
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.FINAL)
|
||||
async def final_step():
|
||||
"""Final code generation step to configure optional logger features."""
|
||||
if CORE.data.get(DOMAIN, {}).get(KEY_LEVEL_LISTENERS, False):
|
||||
cg.add_define("USE_LOGGER_LEVEL_LISTENERS")
|
||||
|
||||
@@ -140,9 +140,8 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
|
||||
uint16_t msg_length =
|
||||
this->tx_buffer_at_ - msg_start; // Don't subtract 1 - tx_buffer_at_ is already at the null terminator position
|
||||
|
||||
// Listeners get message first (before console write)
|
||||
for (auto *listener : this->log_listeners_)
|
||||
listener->on_log(level, tag, this->tx_buffer_ + msg_start, msg_length);
|
||||
// Callbacks get message first (before console write)
|
||||
this->log_callback_.call(level, tag, this->tx_buffer_ + msg_start, msg_length);
|
||||
|
||||
// Write to console starting at the msg_start
|
||||
this->write_tx_buffer_to_console_(msg_start, &msg_length);
|
||||
@@ -204,8 +203,7 @@ void Logger::process_messages_() {
|
||||
this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_);
|
||||
this->tx_buffer_[this->tx_buffer_at_] = '\0';
|
||||
size_t msg_len = this->tx_buffer_at_; // We already know the length from tx_buffer_at_
|
||||
for (auto *listener : this->log_listeners_)
|
||||
listener->on_log(message->level, message->tag, this->tx_buffer_, msg_len);
|
||||
this->log_callback_.call(message->level, message->tag, this->tx_buffer_, msg_len);
|
||||
// At this point all the data we need from message has been transferred to the tx_buffer
|
||||
// so we can release the message to allow other tasks to use it as soon as possible.
|
||||
this->log_buffer_->release_message_main_loop(received_token);
|
||||
@@ -233,6 +231,9 @@ void Logger::set_log_level(const char *tag, uint8_t log_level) { this->log_level
|
||||
UARTSelection Logger::get_uart() const { return this->uart_; }
|
||||
#endif
|
||||
|
||||
void Logger::add_on_log_callback(std::function<void(uint8_t, const char *, const char *, size_t)> &&callback) {
|
||||
this->log_callback_.add(std::move(callback));
|
||||
}
|
||||
float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
|
||||
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
@@ -288,10 +289,7 @@ void Logger::set_log_level(uint8_t level) {
|
||||
ESP_LOGW(TAG, "Cannot set log level higher than pre-compiled %s", LOG_STR_ARG(LOG_LEVELS[ESPHOME_LOG_LEVEL]));
|
||||
}
|
||||
this->current_level_ = level;
|
||||
#ifdef USE_LOGGER_LEVEL_LISTENERS
|
||||
for (auto *listener : this->level_listeners_)
|
||||
listener->on_log_level_change(level);
|
||||
#endif
|
||||
this->level_callback_.call(level);
|
||||
}
|
||||
|
||||
Logger *global_logger = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
@@ -36,52 +36,6 @@ struct device;
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
/** Interface for receiving log messages without std::function overhead.
|
||||
*
|
||||
* Components can implement this interface instead of using lambdas with std::function
|
||||
* to reduce flash usage from std::function type erasure machinery.
|
||||
*
|
||||
* Usage:
|
||||
* class MyComponent : public Component, public LogListener {
|
||||
* public:
|
||||
* void setup() override {
|
||||
* if (logger::global_logger != nullptr)
|
||||
* logger::global_logger->add_log_listener(this);
|
||||
* }
|
||||
* void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override {
|
||||
* // Handle log message
|
||||
* }
|
||||
* };
|
||||
*/
|
||||
class LogListener {
|
||||
public:
|
||||
virtual void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) = 0;
|
||||
};
|
||||
|
||||
#ifdef USE_LOGGER_LEVEL_LISTENERS
|
||||
/** Interface for receiving log level changes without std::function overhead.
|
||||
*
|
||||
* Components can implement this interface instead of using lambdas with std::function
|
||||
* to reduce flash usage from std::function type erasure machinery.
|
||||
*
|
||||
* Usage:
|
||||
* class MyComponent : public Component, public LoggerLevelListener {
|
||||
* public:
|
||||
* void setup() override {
|
||||
* if (logger::global_logger != nullptr)
|
||||
* logger::global_logger->add_logger_level_listener(this);
|
||||
* }
|
||||
* void on_log_level_change(uint8_t level) override {
|
||||
* // Handle log level change
|
||||
* }
|
||||
* };
|
||||
*/
|
||||
class LoggerLevelListener {
|
||||
public:
|
||||
virtual void on_log_level_change(uint8_t level) = 0;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
|
||||
// Comparison function for const char* keys in log_levels_ map
|
||||
struct CStrCompare {
|
||||
@@ -214,13 +168,11 @@ class Logger : public Component {
|
||||
|
||||
inline uint8_t level_for(const char *tag);
|
||||
|
||||
/// Register a log listener to receive log messages
|
||||
void add_log_listener(LogListener *listener) { this->log_listeners_.push_back(listener); }
|
||||
/// Register a callback that will be called for every log message sent
|
||||
void add_on_log_callback(std::function<void(uint8_t, const char *, const char *, size_t)> &&callback);
|
||||
|
||||
#ifdef USE_LOGGER_LEVEL_LISTENERS
|
||||
/// Register a listener for log level changes
|
||||
void add_level_listener(LoggerLevelListener *listener) { this->level_listeners_.push_back(listener); }
|
||||
#endif
|
||||
// add a listener for log level changes
|
||||
void add_listener(std::function<void(uint8_t)> &&callback) { this->level_callback_.add(std::move(callback)); }
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
@@ -288,7 +240,7 @@ class Logger : public Component {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to format and send a log message to both console and listeners
|
||||
// Helper to format and send a log message to both console and callbacks
|
||||
inline void HOT log_message_to_buffer_and_send_(uint8_t level, const char *tag, int line, const char *format,
|
||||
va_list args) {
|
||||
// Format to tx_buffer and prepare for output
|
||||
@@ -296,9 +248,8 @@ class Logger : public Component {
|
||||
this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, this->tx_buffer_, &this->tx_buffer_at_,
|
||||
this->tx_buffer_size_);
|
||||
|
||||
// Listeners get message WITHOUT newline (for API/MQTT/syslog)
|
||||
for (auto *listener : this->log_listeners_)
|
||||
listener->on_log(level, tag, this->tx_buffer_, this->tx_buffer_at_);
|
||||
// Callbacks get message WITHOUT newline (for API/MQTT/syslog)
|
||||
this->log_callback_.call(level, tag, this->tx_buffer_, this->tx_buffer_at_);
|
||||
|
||||
// Console gets message WITH newline (if platform needs it)
|
||||
this->write_tx_buffer_to_console_();
|
||||
@@ -350,10 +301,8 @@ class Logger : public Component {
|
||||
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
|
||||
std::map<const char *, uint8_t, CStrCompare> log_levels_{};
|
||||
#endif
|
||||
std::vector<LogListener *> log_listeners_; // Log message listeners (API, MQTT, syslog, etc.)
|
||||
#ifdef USE_LOGGER_LEVEL_LISTENERS
|
||||
std::vector<LoggerLevelListener *> level_listeners_; // Log level change listeners
|
||||
#endif
|
||||
CallbackManager<void(uint8_t, const char *, const char *, size_t)> log_callback_{};
|
||||
CallbackManager<void(uint8_t)> level_callback_{};
|
||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||
std::unique_ptr<logger::TaskLogBuffer> log_buffer_; // Will be initialized with init_log_buffer
|
||||
#endif
|
||||
@@ -547,15 +496,15 @@ class Logger : public Component {
|
||||
};
|
||||
extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
class LoggerMessageTrigger : public Trigger<uint8_t, const char *, const char *>, public LogListener {
|
||||
class LoggerMessageTrigger : public Trigger<uint8_t, const char *, const char *> {
|
||||
public:
|
||||
explicit LoggerMessageTrigger(Logger *parent, uint8_t level) : level_(level) { parent->add_log_listener(this); }
|
||||
|
||||
void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override {
|
||||
(void) message_len;
|
||||
if (level <= this->level_) {
|
||||
this->trigger(level, tag, message);
|
||||
}
|
||||
explicit LoggerMessageTrigger(Logger *parent, uint8_t level) {
|
||||
this->level_ = level;
|
||||
parent->add_on_log_callback([this](uint8_t level, const char *tag, const char *message, size_t message_len) {
|
||||
if (level <= this->level_) {
|
||||
this->trigger(level, tag, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
@@ -5,13 +5,7 @@ from esphome.const import CONF_LEVEL, CONF_LOGGER, ENTITY_CATEGORY_CONFIG, ICON_
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_helpers import register_component, register_parented
|
||||
|
||||
from .. import (
|
||||
CONF_LOGGER_ID,
|
||||
LOG_LEVELS,
|
||||
Logger,
|
||||
logger_ns,
|
||||
request_logger_level_listeners,
|
||||
)
|
||||
from .. import CONF_LOGGER_ID, LOG_LEVELS, Logger, logger_ns
|
||||
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
|
||||
@@ -27,7 +21,6 @@ CONFIG_SCHEMA = select.select_schema(
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
request_logger_level_listeners()
|
||||
parent = await cg.get_variable(config[CONF_LOGGER_ID])
|
||||
levels = list(LOG_LEVELS)
|
||||
index = levels.index(CORE.data[CONF_LOGGER][CONF_LEVEL])
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
void LoggerLevelSelect::on_log_level_change(uint8_t level) {
|
||||
void LoggerLevelSelect::publish_state(int level) {
|
||||
auto index = level_to_index(level);
|
||||
if (!this->has_index(index))
|
||||
return;
|
||||
@@ -10,8 +10,8 @@ void LoggerLevelSelect::on_log_level_change(uint8_t level) {
|
||||
}
|
||||
|
||||
void LoggerLevelSelect::setup() {
|
||||
this->parent_->add_level_listener(this);
|
||||
this->on_log_level_change(this->parent_->get_log_level());
|
||||
this->parent_->add_listener([this](int level) { this->publish_state(level); });
|
||||
this->publish_state(this->parent_->get_log_level());
|
||||
}
|
||||
|
||||
void LoggerLevelSelect::control(size_t index) { this->parent_->set_log_level(index_to_level(index)); }
|
||||
|
||||
@@ -5,17 +5,12 @@
|
||||
#include "esphome/components/logger/logger.h"
|
||||
|
||||
namespace esphome::logger {
|
||||
class LoggerLevelSelect final : public Component,
|
||||
public select::Select,
|
||||
public Parented<Logger>,
|
||||
public LoggerLevelListener {
|
||||
class LoggerLevelSelect : public Component, public select::Select, public Parented<Logger> {
|
||||
public:
|
||||
void publish_state(int level);
|
||||
void setup() override;
|
||||
void control(size_t index) override;
|
||||
|
||||
// LoggerLevelListener interface
|
||||
void on_log_level_change(uint8_t level) override;
|
||||
|
||||
protected:
|
||||
// Convert log level to option index (skip CONFIG at level 4)
|
||||
static uint8_t level_to_index(uint8_t level) { return (level > ESPHOME_LOG_LEVEL_CONFIG) ? level - 1 : level; }
|
||||
|
||||
@@ -57,7 +57,15 @@ void MQTTClientComponent::setup() {
|
||||
});
|
||||
#ifdef USE_LOGGER
|
||||
if (this->is_log_message_enabled() && logger::global_logger != nullptr) {
|
||||
logger::global_logger->add_log_listener(this);
|
||||
logger::global_logger->add_on_log_callback(
|
||||
[this](int level, const char *tag, const char *message, size_t message_len) {
|
||||
if (level <= this->log_level_ && this->is_connected()) {
|
||||
this->publish({.topic = this->log_message_.topic,
|
||||
.payload = std::string(message, message_len),
|
||||
.qos = this->log_message_.qos,
|
||||
.retain = this->log_message_.retain});
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -140,18 +148,6 @@ void MQTTClientComponent::send_device_info_() {
|
||||
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
}
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
void MQTTClientComponent::on_log(uint8_t level, const char *tag, const char *message, size_t message_len) {
|
||||
(void) tag;
|
||||
if (level <= this->log_level_ && this->is_connected()) {
|
||||
this->publish({.topic = this->log_message_.topic,
|
||||
.payload = std::string(message, message_len),
|
||||
.qos = this->log_message_.qos,
|
||||
.retain = this->log_message_.retain});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void MQTTClientComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"MQTT:\n"
|
||||
|
||||
@@ -10,9 +10,6 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#ifdef USE_LOGGER
|
||||
#include "esphome/components/logger/logger.h"
|
||||
#endif
|
||||
#if defined(USE_ESP32)
|
||||
#include "mqtt_backend_esp32.h"
|
||||
#elif defined(USE_ESP8266)
|
||||
@@ -100,12 +97,7 @@ enum MQTTClientState {
|
||||
|
||||
class MQTTComponent;
|
||||
|
||||
class MQTTClientComponent : public Component
|
||||
#ifdef USE_LOGGER
|
||||
,
|
||||
public logger::LogListener
|
||||
#endif
|
||||
{
|
||||
class MQTTClientComponent : public Component {
|
||||
public:
|
||||
MQTTClientComponent();
|
||||
|
||||
@@ -246,10 +238,6 @@ class MQTTClientComponent : public Component
|
||||
/// MQTT client setup priority
|
||||
float get_setup_priority() const override;
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override;
|
||||
#endif
|
||||
|
||||
void on_message(const std::string &topic, const std::string &payload);
|
||||
|
||||
bool can_proceed() override;
|
||||
|
||||
@@ -2,8 +2,7 @@ import logging
|
||||
from pathlib import Path
|
||||
|
||||
from esphome import git, yaml_util
|
||||
from esphome.components.substitutions.jinja import has_jinja
|
||||
from esphome.config_helpers import Remove, merge_config
|
||||
from esphome.config_helpers import merge_config
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ESPHOME,
|
||||
@@ -40,15 +39,10 @@ def valid_package_contents(package_config: dict):
|
||||
for k, v in package_config.items():
|
||||
if not isinstance(k, str):
|
||||
raise cv.Invalid("Package content keys must be strings")
|
||||
if isinstance(v, (dict, list, Remove)):
|
||||
continue # e.g. script: [], psram: !remove, logger: {level: debug}
|
||||
if isinstance(v, (dict, list)):
|
||||
continue # e.g. script: [] or logger: {level: debug}
|
||||
if v is None:
|
||||
continue # e.g. web_server:
|
||||
if isinstance(v, str) and has_jinja(v):
|
||||
# e.g: remote package shorthand:
|
||||
# package_name: github://esphome/repo/file.yaml@${ branch }
|
||||
continue
|
||||
|
||||
raise cv.Invalid("Invalid component content in package definition")
|
||||
return package_config
|
||||
|
||||
|
||||
@@ -64,15 +64,21 @@ void MR24HPC1Component::dump_config() {
|
||||
void MR24HPC1Component::setup() {
|
||||
this->check_uart_settings(115200);
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
if (this->custom_mode_number_ != nullptr) {
|
||||
this->custom_mode_number_->publish_state(0); // Zero out the custom mode
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
if (this->custom_mode_num_sensor_ != nullptr) {
|
||||
this->custom_mode_num_sensor_->publish_state(0);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->custom_mode_end_text_sensor_ != nullptr) {
|
||||
this->custom_mode_end_text_sensor_->publish_state("Not in custom mode");
|
||||
}
|
||||
#endif
|
||||
this->set_custom_end_mode();
|
||||
this->poll_time_base_func_check_ = true;
|
||||
this->check_dev_inf_sign_ = true;
|
||||
@@ -352,6 +358,7 @@ void MR24HPC1Component::r24_split_data_frame_(uint8_t value) {
|
||||
}
|
||||
|
||||
// Parses data frames related to product information
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void MR24HPC1Component::r24_frame_parse_product_information_(uint8_t *data) {
|
||||
uint16_t product_len = encode_uint16(data[FRAME_COMMAND_WORD_INDEX + 1], data[FRAME_COMMAND_WORD_INDEX + 2]);
|
||||
if (data[FRAME_COMMAND_WORD_INDEX] == COMMAND_PRODUCT_MODE) {
|
||||
@@ -389,20 +396,29 @@ void MR24HPC1Component::r24_frame_parse_product_information_(uint8_t *data) {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Parsing the underlying open parameters
|
||||
void MR24HPC1Component::r24_frame_parse_open_underlying_information_(uint8_t *data) {
|
||||
if (data[FRAME_COMMAND_WORD_INDEX] == 0x00) {
|
||||
uint8_t cmd = data[FRAME_COMMAND_WORD_INDEX];
|
||||
|
||||
if (cmd == 0x00) {
|
||||
#ifdef USE_SWITCH
|
||||
if (this->underlying_open_function_switch_ != nullptr) {
|
||||
this->underlying_open_function_switch_->publish_state(
|
||||
data[FRAME_DATA_INDEX]); // Underlying Open Parameter Switch Status Updates
|
||||
}
|
||||
#endif
|
||||
if (data[FRAME_DATA_INDEX]) {
|
||||
this->s_output_info_switch_flag_ = OUTPUT_SWITCH_ON;
|
||||
} else {
|
||||
this->s_output_info_switch_flag_ = OUTPUT_SWTICH_OFF;
|
||||
}
|
||||
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x01) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == 0x01) {
|
||||
#ifdef USE_SENSOR
|
||||
if (this->custom_spatial_static_value_sensor_ != nullptr) {
|
||||
this->custom_spatial_static_value_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
@@ -418,72 +434,125 @@ void MR24HPC1Component::r24_frame_parse_open_underlying_information_(uint8_t *da
|
||||
if (this->custom_motion_speed_sensor_ != nullptr) {
|
||||
this->custom_motion_speed_sensor_->publish_state((data[FRAME_DATA_INDEX + 4] - 10) * 0.5f);
|
||||
}
|
||||
} else if ((data[FRAME_COMMAND_WORD_INDEX] == 0x06) || (data[FRAME_COMMAND_WORD_INDEX] == 0x86)) {
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == 0x06 || cmd == 0x86) {
|
||||
// none:0x00 close_to:0x01 far_away:0x02
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if ((this->keep_away_text_sensor_ != nullptr) && (data[FRAME_DATA_INDEX] < 3)) {
|
||||
this->keep_away_text_sensor_->publish_state(S_KEEP_AWAY_STR[data[FRAME_DATA_INDEX]]);
|
||||
}
|
||||
} else if ((this->movement_signs_sensor_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x07) || (data[FRAME_COMMAND_WORD_INDEX] == 0x87))) {
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
if ((cmd == 0x07 || cmd == 0x87) && this->movement_signs_sensor_ != nullptr) {
|
||||
this->movement_signs_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
} else if ((this->existence_threshold_number_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x08) || (data[FRAME_COMMAND_WORD_INDEX] == 0x88))) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
if ((cmd == 0x08 || cmd == 0x88) && this->existence_threshold_number_ != nullptr) {
|
||||
this->existence_threshold_number_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
} else if ((this->motion_threshold_number_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x09) || (data[FRAME_COMMAND_WORD_INDEX] == 0x89))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((cmd == 0x09 || cmd == 0x89) && this->motion_threshold_number_ != nullptr) {
|
||||
this->motion_threshold_number_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
} else if ((this->existence_boundary_select_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x0a) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8a))) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
if ((cmd == 0x0a || cmd == 0x8a) && this->existence_boundary_select_ != nullptr) {
|
||||
if (this->existence_boundary_select_->has_index(data[FRAME_DATA_INDEX] - 1)) {
|
||||
this->existence_boundary_select_->publish_state(data[FRAME_DATA_INDEX] - 1);
|
||||
}
|
||||
} else if ((this->motion_boundary_select_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x0b) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8b))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((cmd == 0x0b || cmd == 0x8b) && this->motion_boundary_select_ != nullptr) {
|
||||
if (this->motion_boundary_select_->has_index(data[FRAME_DATA_INDEX] - 1)) {
|
||||
this->motion_boundary_select_->publish_state(data[FRAME_DATA_INDEX] - 1);
|
||||
}
|
||||
} else if ((this->motion_trigger_number_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x0c) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8c))) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
if ((cmd == 0x0c || cmd == 0x8c) && this->motion_trigger_number_ != nullptr) {
|
||||
uint32_t motion_trigger_time = encode_uint32(data[FRAME_DATA_INDEX], data[FRAME_DATA_INDEX + 1],
|
||||
data[FRAME_DATA_INDEX + 2], data[FRAME_DATA_INDEX + 3]);
|
||||
this->motion_trigger_number_->publish_state(motion_trigger_time);
|
||||
} else if ((this->motion_to_rest_number_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x0d) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8d))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((cmd == 0x0d || cmd == 0x8d) && this->motion_to_rest_number_ != nullptr) {
|
||||
uint32_t move_to_rest_time = encode_uint32(data[FRAME_DATA_INDEX], data[FRAME_DATA_INDEX + 1],
|
||||
data[FRAME_DATA_INDEX + 2], data[FRAME_DATA_INDEX + 3]);
|
||||
this->motion_to_rest_number_->publish_state(move_to_rest_time);
|
||||
} else if ((this->custom_unman_time_number_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x0e) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8e))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((cmd == 0x0e || cmd == 0x8e) && this->custom_unman_time_number_ != nullptr) {
|
||||
uint32_t enter_unmanned_time = encode_uint32(data[FRAME_DATA_INDEX], data[FRAME_DATA_INDEX + 1],
|
||||
data[FRAME_DATA_INDEX + 2], data[FRAME_DATA_INDEX + 3]);
|
||||
float custom_unmanned_time = enter_unmanned_time / 1000.0;
|
||||
this->custom_unman_time_number_->publish_state(custom_unmanned_time);
|
||||
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x80) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (cmd == 0x80) {
|
||||
if (data[FRAME_DATA_INDEX]) {
|
||||
this->s_output_info_switch_flag_ = OUTPUT_SWITCH_ON;
|
||||
} else {
|
||||
this->s_output_info_switch_flag_ = OUTPUT_SWTICH_OFF;
|
||||
}
|
||||
#ifdef USE_SWITCH
|
||||
if (this->underlying_open_function_switch_ != nullptr) {
|
||||
this->underlying_open_function_switch_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
} else if ((this->custom_spatial_static_value_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x81)) {
|
||||
this->custom_spatial_static_value_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
} else if ((this->custom_spatial_motion_value_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x82)) {
|
||||
this->custom_spatial_motion_value_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
} else if ((this->custom_presence_of_detection_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x83)) {
|
||||
this->custom_presence_of_detection_sensor_->publish_state(
|
||||
S_PRESENCE_OF_DETECTION_RANGE_STR[data[FRAME_DATA_INDEX]]);
|
||||
} else if ((this->custom_motion_distance_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x84)) {
|
||||
this->custom_motion_distance_sensor_->publish_state(data[FRAME_DATA_INDEX] * 0.5f);
|
||||
} else if ((this->custom_motion_speed_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x85)) {
|
||||
this->custom_motion_speed_sensor_->publish_state((data[FRAME_DATA_INDEX] - 10) * 0.5f);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
if (cmd == 0x81 && this->custom_spatial_static_value_sensor_ != nullptr) {
|
||||
this->custom_spatial_static_value_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == 0x82 && this->custom_spatial_motion_value_sensor_ != nullptr) {
|
||||
this->custom_spatial_motion_value_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == 0x83 && this->custom_presence_of_detection_sensor_ != nullptr) {
|
||||
this->custom_presence_of_detection_sensor_->publish_state(S_PRESENCE_OF_DETECTION_RANGE_STR[data[FRAME_DATA_INDEX]]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == 0x84 && this->custom_motion_distance_sensor_ != nullptr) {
|
||||
this->custom_motion_distance_sensor_->publish_state(data[FRAME_DATA_INDEX] * 0.5f);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == 0x85 && this->custom_motion_speed_sensor_ != nullptr) {
|
||||
this->custom_motion_speed_sensor_->publish_state((data[FRAME_DATA_INDEX] - 10) * 0.5f);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void MR24HPC1Component::r24_parse_data_frame_(uint8_t *data, uint8_t len) {
|
||||
switch (data[FRAME_CONTROL_WORD_INDEX]) {
|
||||
case 0x01: {
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if ((this->heartbeat_state_text_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x01)) {
|
||||
this->heartbeat_state_text_sensor_->publish_state("Equipment Normal");
|
||||
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x02) {
|
||||
@@ -491,9 +560,16 @@ void MR24HPC1Component::r24_parse_data_frame_(uint8_t *data, uint8_t len) {
|
||||
} else if (this->heartbeat_state_text_sensor_ != nullptr) {
|
||||
this->heartbeat_state_text_sensor_->publish_state("Equipment Abnormal");
|
||||
}
|
||||
#else
|
||||
if (data[FRAME_COMMAND_WORD_INDEX] == 0x02) {
|
||||
ESP_LOGD(TAG, "Reply: query restart packet");
|
||||
}
|
||||
#endif
|
||||
} break;
|
||||
case 0x02: {
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
this->r24_frame_parse_product_information_(data);
|
||||
#endif
|
||||
} break;
|
||||
case 0x05: {
|
||||
this->r24_frame_parse_work_status_(data);
|
||||
@@ -511,87 +587,152 @@ void MR24HPC1Component::r24_parse_data_frame_(uint8_t *data, uint8_t len) {
|
||||
}
|
||||
|
||||
void MR24HPC1Component::r24_frame_parse_work_status_(uint8_t *data) {
|
||||
if (data[FRAME_COMMAND_WORD_INDEX] == 0x01) {
|
||||
uint8_t cmd = data[FRAME_COMMAND_WORD_INDEX];
|
||||
|
||||
if (cmd == 0x01) {
|
||||
ESP_LOGD(TAG, "Reply: get radar init status 0x%02X", data[FRAME_DATA_INDEX]);
|
||||
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x07) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == 0x07) {
|
||||
#ifdef USE_SELECT
|
||||
if ((this->scene_mode_select_ != nullptr) && (this->scene_mode_select_->has_index(data[FRAME_DATA_INDEX]))) {
|
||||
this->scene_mode_select_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Select has index offset %d Error", data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
} else if ((this->sensitivity_number_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x08) || (data[FRAME_COMMAND_WORD_INDEX] == 0x88))) {
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
if ((cmd == 0x08 || cmd == 0x88) && this->sensitivity_number_ != nullptr) {
|
||||
// 1-3
|
||||
this->sensitivity_number_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x09) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (cmd == 0x09) {
|
||||
// 1-4
|
||||
#ifdef USE_SENSOR
|
||||
if (this->custom_mode_num_sensor_ != nullptr) {
|
||||
this->custom_mode_num_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
if (this->custom_mode_number_ != nullptr) {
|
||||
this->custom_mode_number_->publish_state(0);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->custom_mode_end_text_sensor_ != nullptr) {
|
||||
this->custom_mode_end_text_sensor_->publish_state("Setup in progress");
|
||||
}
|
||||
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x81) {
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == 0x81) {
|
||||
ESP_LOGD(TAG, "Reply: get radar init status 0x%02X", data[FRAME_DATA_INDEX]);
|
||||
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x87) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == 0x87) {
|
||||
#ifdef USE_SELECT
|
||||
if ((this->scene_mode_select_ != nullptr) && (this->scene_mode_select_->has_index(data[FRAME_DATA_INDEX]))) {
|
||||
this->scene_mode_select_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Select has index offset %d Error", data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
} else if ((this->custom_mode_end_text_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x0A)) {
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (cmd == 0x0A && this->custom_mode_end_text_sensor_ != nullptr) {
|
||||
this->custom_mode_end_text_sensor_->publish_state("Set Success!");
|
||||
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x89) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (cmd == 0x89) {
|
||||
if (data[FRAME_DATA_INDEX] == 0) {
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->custom_mode_end_text_sensor_ != nullptr) {
|
||||
this->custom_mode_end_text_sensor_->publish_state("Not in custom mode");
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
if (this->custom_mode_number_ != nullptr) {
|
||||
this->custom_mode_number_->publish_state(0);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
if (this->custom_mode_num_sensor_ != nullptr) {
|
||||
this->custom_mode_num_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
#ifdef USE_SENSOR
|
||||
if (this->custom_mode_num_sensor_ != nullptr) {
|
||||
this->custom_mode_num_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
ESP_LOGD(TAG, "[%s] No found COMMAND_WORD(%02X) in Frame", __FUNCTION__, data[FRAME_COMMAND_WORD_INDEX]);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "[%s] No found COMMAND_WORD(%02X) in Frame", __FUNCTION__, cmd);
|
||||
}
|
||||
|
||||
void MR24HPC1Component::r24_frame_parse_human_information_(uint8_t *data) {
|
||||
if ((this->has_target_binary_sensor_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x01) || (data[FRAME_COMMAND_WORD_INDEX] == 0x81))) {
|
||||
uint8_t cmd = data[FRAME_COMMAND_WORD_INDEX];
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
if ((cmd == 0x01 || cmd == 0x81) && this->has_target_binary_sensor_ != nullptr) {
|
||||
this->has_target_binary_sensor_->publish_state(S_SOMEONE_EXISTS_STR[data[FRAME_DATA_INDEX]]);
|
||||
} else if ((this->motion_status_text_sensor_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x02) || (data[FRAME_COMMAND_WORD_INDEX] == 0x82))) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if ((cmd == 0x02 || cmd == 0x82) && this->motion_status_text_sensor_ != nullptr) {
|
||||
if (data[FRAME_DATA_INDEX] < 3) {
|
||||
this->motion_status_text_sensor_->publish_state(S_MOTION_STATUS_STR[data[FRAME_DATA_INDEX]]);
|
||||
}
|
||||
} else if ((this->movement_signs_sensor_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x03) || (data[FRAME_COMMAND_WORD_INDEX] == 0x83))) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
if ((cmd == 0x03 || cmd == 0x83) && this->movement_signs_sensor_ != nullptr) {
|
||||
this->movement_signs_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
} else if ((this->unman_time_select_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x0A) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8A))) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
if ((cmd == 0x0A || cmd == 0x8A) && this->unman_time_select_ != nullptr) {
|
||||
// none:0x00 1s:0x01 30s:0x02 1min:0x03 2min:0x04 5min:0x05 10min:0x06 30min:0x07 1hour:0x08
|
||||
if (data[FRAME_DATA_INDEX] < 9) {
|
||||
this->unman_time_select_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
} else if ((this->keep_away_text_sensor_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x0B) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8B))) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if ((cmd == 0x0B || cmd == 0x8B) && this->keep_away_text_sensor_ != nullptr) {
|
||||
// none:0x00 close_to:0x01 far_away:0x02
|
||||
if (data[FRAME_DATA_INDEX] < 3) {
|
||||
this->keep_away_text_sensor_->publish_state(S_KEEP_AWAY_STR[data[FRAME_DATA_INDEX]]);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGD(TAG, "[%s] No found COMMAND_WORD(%02X) in Frame", __FUNCTION__, data[FRAME_COMMAND_WORD_INDEX]);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
ESP_LOGD(TAG, "[%s] No found COMMAND_WORD(%02X) in Frame", __FUNCTION__, cmd);
|
||||
}
|
||||
|
||||
// Sending data frames
|
||||
@@ -695,12 +836,15 @@ void MR24HPC1Component::set_underlying_open_function(bool enable) {
|
||||
} else {
|
||||
this->send_query_(UNDERLYING_SWITCH_OFF, sizeof(UNDERLYING_SWITCH_OFF));
|
||||
}
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->keep_away_text_sensor_ != nullptr) {
|
||||
this->keep_away_text_sensor_->publish_state("");
|
||||
}
|
||||
if (this->motion_status_text_sensor_ != nullptr) {
|
||||
this->motion_status_text_sensor_->publish_state("");
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
if (this->custom_spatial_static_value_sensor_ != nullptr) {
|
||||
this->custom_spatial_static_value_sensor_->publish_state(NAN);
|
||||
}
|
||||
@@ -716,6 +860,7 @@ void MR24HPC1Component::set_underlying_open_function(bool enable) {
|
||||
if (this->custom_motion_speed_sensor_ != nullptr) {
|
||||
this->custom_motion_speed_sensor_->publish_state(NAN);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void MR24HPC1Component::set_scene_mode(uint8_t value) {
|
||||
@@ -723,12 +868,16 @@ void MR24HPC1Component::set_scene_mode(uint8_t value) {
|
||||
uint8_t send_data[10] = {0x53, 0x59, 0x05, 0x07, 0x00, 0x01, value, 0x00, 0x54, 0x43};
|
||||
send_data[7] = get_frame_crc_sum(send_data, send_data_len);
|
||||
this->send_query_(send_data, send_data_len);
|
||||
#ifdef USE_NUMBER
|
||||
if (this->custom_mode_number_ != nullptr) {
|
||||
this->custom_mode_number_->publish_state(0);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
if (this->custom_mode_num_sensor_ != nullptr) {
|
||||
this->custom_mode_num_sensor_->publish_state(0);
|
||||
}
|
||||
#endif
|
||||
this->get_scene_mode();
|
||||
this->get_sensitivity();
|
||||
this->get_custom_mode();
|
||||
@@ -768,9 +917,11 @@ void MR24HPC1Component::set_unman_time(uint8_t value) {
|
||||
void MR24HPC1Component::set_custom_mode(uint8_t mode) {
|
||||
if (mode == 0) {
|
||||
this->set_custom_end_mode(); // Equivalent to end setting
|
||||
#ifdef USE_NUMBER
|
||||
if (this->custom_mode_number_ != nullptr) {
|
||||
this->custom_mode_number_->publish_state(0);
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
uint8_t send_data_len = 10;
|
||||
@@ -793,9 +944,11 @@ void MR24HPC1Component::set_custom_end_mode() {
|
||||
uint8_t send_data_len = 10;
|
||||
uint8_t send_data[10] = {0x53, 0x59, 0x05, 0x0a, 0x00, 0x01, 0x0F, 0xCB, 0x54, 0x43};
|
||||
this->send_query_(send_data, send_data_len);
|
||||
#ifdef USE_NUMBER
|
||||
if (this->custom_mode_number_ != nullptr) {
|
||||
this->custom_mode_number_->publish_state(0); // Clear setpoints
|
||||
}
|
||||
#endif
|
||||
this->get_existence_boundary();
|
||||
this->get_motion_boundary();
|
||||
this->get_existence_threshold();
|
||||
@@ -809,8 +962,10 @@ void MR24HPC1Component::set_custom_end_mode() {
|
||||
}
|
||||
|
||||
void MR24HPC1Component::set_existence_boundary(uint8_t value) {
|
||||
#ifdef USE_SENSOR
|
||||
if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0))
|
||||
return; // You'll have to check that you're in custom mode to set it up
|
||||
#endif
|
||||
uint8_t send_data_len = 10;
|
||||
uint8_t send_data[10] = {0x53, 0x59, 0x08, 0x0A, 0x00, 0x01, (uint8_t) (value + 1), 0x00, 0x54, 0x43};
|
||||
send_data[7] = get_frame_crc_sum(send_data, send_data_len);
|
||||
@@ -819,8 +974,10 @@ void MR24HPC1Component::set_existence_boundary(uint8_t value) {
|
||||
}
|
||||
|
||||
void MR24HPC1Component::set_motion_boundary(uint8_t value) {
|
||||
#ifdef USE_SENSOR
|
||||
if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0))
|
||||
return; // You'll have to check that you're in custom mode to set it up
|
||||
#endif
|
||||
uint8_t send_data_len = 10;
|
||||
uint8_t send_data[10] = {0x53, 0x59, 0x08, 0x0B, 0x00, 0x01, (uint8_t) (value + 1), 0x00, 0x54, 0x43};
|
||||
send_data[7] = get_frame_crc_sum(send_data, send_data_len);
|
||||
@@ -829,8 +986,10 @@ void MR24HPC1Component::set_motion_boundary(uint8_t value) {
|
||||
}
|
||||
|
||||
void MR24HPC1Component::set_existence_threshold(uint8_t value) {
|
||||
#ifdef USE_SENSOR
|
||||
if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0))
|
||||
return; // You'll have to check that you're in custom mode to set it up
|
||||
#endif
|
||||
uint8_t send_data_len = 10;
|
||||
uint8_t send_data[10] = {0x53, 0x59, 0x08, 0x08, 0x00, 0x01, value, 0x00, 0x54, 0x43};
|
||||
send_data[7] = get_frame_crc_sum(send_data, send_data_len);
|
||||
@@ -839,8 +998,10 @@ void MR24HPC1Component::set_existence_threshold(uint8_t value) {
|
||||
}
|
||||
|
||||
void MR24HPC1Component::set_motion_threshold(uint8_t value) {
|
||||
#ifdef USE_SENSOR
|
||||
if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0))
|
||||
return; // You'll have to check that you're in custom mode to set it up
|
||||
#endif
|
||||
uint8_t send_data_len = 10;
|
||||
uint8_t send_data[10] = {0x53, 0x59, 0x08, 0x09, 0x00, 0x01, value, 0x00, 0x54, 0x43};
|
||||
send_data[7] = get_frame_crc_sum(send_data, send_data_len);
|
||||
@@ -849,8 +1010,10 @@ void MR24HPC1Component::set_motion_threshold(uint8_t value) {
|
||||
}
|
||||
|
||||
void MR24HPC1Component::set_motion_trigger_time(uint8_t value) {
|
||||
#ifdef USE_SENSOR
|
||||
if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0))
|
||||
return; // You'll have to check that you're in custom mode to set it up
|
||||
#endif
|
||||
uint8_t send_data_len = 13;
|
||||
uint8_t send_data[13] = {0x53, 0x59, 0x08, 0x0C, 0x00, 0x04, 0x00, 0x00, 0x00, value, 0x00, 0x54, 0x43};
|
||||
send_data[10] = get_frame_crc_sum(send_data, send_data_len);
|
||||
@@ -859,8 +1022,10 @@ void MR24HPC1Component::set_motion_trigger_time(uint8_t value) {
|
||||
}
|
||||
|
||||
void MR24HPC1Component::set_motion_to_rest_time(uint16_t value) {
|
||||
#ifdef USE_SENSOR
|
||||
if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0))
|
||||
return; // You'll have to check that you're in custom mode to set it up
|
||||
#endif
|
||||
uint8_t h8_num = (value >> 8) & 0xff;
|
||||
uint8_t l8_num = value & 0xff;
|
||||
uint8_t send_data_len = 13;
|
||||
@@ -871,8 +1036,10 @@ void MR24HPC1Component::set_motion_to_rest_time(uint16_t value) {
|
||||
}
|
||||
|
||||
void MR24HPC1Component::set_custom_unman_time(uint16_t value) {
|
||||
#ifdef USE_SENSOR
|
||||
if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0))
|
||||
return; // You'll have to check that you're in custom mode to set it up
|
||||
#endif
|
||||
uint32_t value_ms = value * 1000;
|
||||
uint8_t h24_num = (value_ms >> 24) & 0xff;
|
||||
uint8_t h16_num = (value_ms >> 16) & 0xff;
|
||||
|
||||
@@ -160,7 +160,9 @@ class MR24HPC1Component : public Component,
|
||||
void r24_parse_data_frame_(uint8_t *data, uint8_t len);
|
||||
void r24_frame_parse_open_underlying_information_(uint8_t *data);
|
||||
void r24_frame_parse_work_status_(uint8_t *data);
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void r24_frame_parse_product_information_(uint8_t *data);
|
||||
#endif
|
||||
void r24_frame_parse_human_information_(uint8_t *data);
|
||||
void send_query_(const uint8_t *query, size_t string_length);
|
||||
|
||||
|
||||
@@ -270,9 +270,7 @@ ThrottleFilter = sensor_ns.class_("ThrottleFilter", Filter)
|
||||
ThrottleWithPriorityFilter = sensor_ns.class_(
|
||||
"ThrottleWithPriorityFilter", ValueListFilter
|
||||
)
|
||||
TimeoutFilterBase = sensor_ns.class_("TimeoutFilterBase", Filter, cg.Component)
|
||||
TimeoutFilterLast = sensor_ns.class_("TimeoutFilterLast", TimeoutFilterBase)
|
||||
TimeoutFilterConfigured = sensor_ns.class_("TimeoutFilterConfigured", TimeoutFilterBase)
|
||||
TimeoutFilter = sensor_ns.class_("TimeoutFilter", Filter, cg.Component)
|
||||
DebounceFilter = sensor_ns.class_("DebounceFilter", Filter, cg.Component)
|
||||
HeartbeatFilter = sensor_ns.class_("HeartbeatFilter", Filter, cg.Component)
|
||||
DeltaFilter = sensor_ns.class_("DeltaFilter", Filter)
|
||||
@@ -683,16 +681,11 @@ TIMEOUT_SCHEMA = cv.maybe_simple_value(
|
||||
)
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register("timeout", TimeoutFilterBase, TIMEOUT_SCHEMA)
|
||||
@FILTER_REGISTRY.register("timeout", TimeoutFilter, TIMEOUT_SCHEMA)
|
||||
async def timeout_filter_to_code(config, filter_id):
|
||||
filter_id = filter_id.copy()
|
||||
if config[CONF_VALUE] == "last":
|
||||
# Use TimeoutFilterLast for "last" mode (smaller, more common - LD2450, LD2412, etc.)
|
||||
filter_id.type = TimeoutFilterLast
|
||||
var = cg.new_Pvariable(filter_id, config[CONF_TIMEOUT])
|
||||
else:
|
||||
# Use TimeoutFilterConfigured for configured value mode
|
||||
filter_id.type = TimeoutFilterConfigured
|
||||
template_ = await cg.templatable(config[CONF_VALUE], [], float)
|
||||
var = cg.new_Pvariable(filter_id, config[CONF_TIMEOUT], template_)
|
||||
await cg.register_component(var, {})
|
||||
|
||||
@@ -339,43 +339,20 @@ void OrFilter::initialize(Sensor *parent, Filter *next) {
|
||||
this->phi_.initialize(parent, nullptr);
|
||||
}
|
||||
|
||||
// TimeoutFilterBase - shared loop logic
|
||||
void TimeoutFilterBase::loop() {
|
||||
// Check if timeout period has elapsed
|
||||
// Use cached loop start time to avoid repeated millis() calls
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
if (now - this->timeout_start_time_ >= this->time_period_) {
|
||||
// Timeout fired - get output value from derived class and output it
|
||||
this->output(this->get_output_value());
|
||||
|
||||
// Disable loop until next value arrives
|
||||
this->disable_loop();
|
||||
// TimeoutFilter
|
||||
optional<float> TimeoutFilter::new_value(float value) {
|
||||
if (this->value_.has_value()) {
|
||||
this->set_timeout("timeout", this->time_period_, [this]() { this->output(this->value_.value().value()); });
|
||||
} else {
|
||||
this->set_timeout("timeout", this->time_period_, [this, value]() { this->output(value); });
|
||||
}
|
||||
}
|
||||
|
||||
float TimeoutFilterBase::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
// TimeoutFilterLast - "last" mode implementation
|
||||
optional<float> TimeoutFilterLast::new_value(float value) {
|
||||
// Store the value to output when timeout fires
|
||||
this->pending_value_ = value;
|
||||
|
||||
// Record when timeout started and enable loop
|
||||
this->timeout_start_time_ = millis();
|
||||
this->enable_loop();
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// TimeoutFilterConfigured - configured value mode implementation
|
||||
optional<float> TimeoutFilterConfigured::new_value(float value) {
|
||||
// Record when timeout started and enable loop
|
||||
// Note: we don't store the incoming value since we have a configured value
|
||||
this->timeout_start_time_ = millis();
|
||||
this->enable_loop();
|
||||
|
||||
return value;
|
||||
}
|
||||
TimeoutFilter::TimeoutFilter(uint32_t time_period) : time_period_(time_period) {}
|
||||
TimeoutFilter::TimeoutFilter(uint32_t time_period, const TemplatableValue<float> &new_value)
|
||||
: time_period_(time_period), value_(new_value) {}
|
||||
float TimeoutFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
// DebounceFilter
|
||||
optional<float> DebounceFilter::new_value(float value) {
|
||||
|
||||
@@ -380,46 +380,18 @@ class ThrottleWithPriorityFilter : public ValueListFilter {
|
||||
uint32_t min_time_between_inputs_;
|
||||
};
|
||||
|
||||
// Base class for timeout filters - contains common loop logic
|
||||
class TimeoutFilterBase : public Filter, public Component {
|
||||
class TimeoutFilter : public Filter, public Component {
|
||||
public:
|
||||
void loop() override;
|
||||
explicit TimeoutFilter(uint32_t time_period);
|
||||
explicit TimeoutFilter(uint32_t time_period, const TemplatableValue<float> &new_value);
|
||||
|
||||
optional<float> new_value(float value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
explicit TimeoutFilterBase(uint32_t time_period) : time_period_(time_period) { this->disable_loop(); }
|
||||
virtual float get_output_value() = 0;
|
||||
|
||||
uint32_t time_period_; // 4 bytes (timeout duration in ms)
|
||||
uint32_t timeout_start_time_{0}; // 4 bytes (when the timeout was started)
|
||||
// Total base: 8 bytes
|
||||
};
|
||||
|
||||
// Timeout filter for "last" mode - outputs the last received value after timeout
|
||||
class TimeoutFilterLast : public TimeoutFilterBase {
|
||||
public:
|
||||
explicit TimeoutFilterLast(uint32_t time_period) : TimeoutFilterBase(time_period) {}
|
||||
|
||||
optional<float> new_value(float value) override;
|
||||
|
||||
protected:
|
||||
float get_output_value() override { return this->pending_value_; }
|
||||
float pending_value_{0}; // 4 bytes (value to output when timeout fires)
|
||||
// Total: 8 (base) + 4 = 12 bytes + vtable ptr + Component overhead
|
||||
};
|
||||
|
||||
// Timeout filter with configured value - evaluates TemplatableValue after timeout
|
||||
class TimeoutFilterConfigured : public TimeoutFilterBase {
|
||||
public:
|
||||
explicit TimeoutFilterConfigured(uint32_t time_period, const TemplatableValue<float> &new_value)
|
||||
: TimeoutFilterBase(time_period), value_(new_value) {}
|
||||
|
||||
optional<float> new_value(float value) override;
|
||||
|
||||
protected:
|
||||
float get_output_value() override { return this->value_.value(); }
|
||||
TemplatableValue<float> value_; // 16 bytes (configured output value, can be lambda)
|
||||
// Total: 8 (base) + 16 = 24 bytes + vtable ptr + Component overhead
|
||||
uint32_t time_period_;
|
||||
optional<TemplatableValue<float>> value_;
|
||||
};
|
||||
|
||||
class DebounceFilter : public Filter, public Component {
|
||||
|
||||
@@ -19,10 +19,11 @@ constexpr int LOG_LEVEL_TO_SYSLOG_SEVERITY[] = {
|
||||
7 // VERY_VERBOSE
|
||||
};
|
||||
|
||||
void Syslog::setup() { logger::global_logger->add_log_listener(this); }
|
||||
|
||||
void Syslog::on_log(uint8_t level, const char *tag, const char *message, size_t message_len) {
|
||||
this->log_(level, tag, message, message_len);
|
||||
void Syslog::setup() {
|
||||
logger::global_logger->add_on_log_callback(
|
||||
[this](int level, const char *tag, const char *message, size_t message_len) {
|
||||
this->log_(level, tag, message, message_len);
|
||||
});
|
||||
}
|
||||
|
||||
void Syslog::log_(const int level, const char *tag, const char *message, size_t message_len) const {
|
||||
|
||||
@@ -2,18 +2,16 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/logger/logger.h"
|
||||
#include "esphome/components/udp/udp_component.h"
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
|
||||
#ifdef USE_NETWORK
|
||||
namespace esphome {
|
||||
namespace syslog {
|
||||
class Syslog : public Component, public Parented<udp::UDPComponent>, public logger::LogListener {
|
||||
class Syslog : public Component, public Parented<udp::UDPComponent> {
|
||||
public:
|
||||
Syslog(int level, time::RealTimeClock *time) : log_level_(level), time_(time) {}
|
||||
void setup() override;
|
||||
void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override;
|
||||
void set_strip(bool strip) { this->strip_ = strip; }
|
||||
void set_facility(int facility) { this->facility_ = facility; }
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import socket
|
||||
from esphome.components.uart import (
|
||||
CONF_DATA_BITS,
|
||||
CONF_PARITY,
|
||||
@@ -18,7 +17,7 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.cpp_types import Component
|
||||
|
||||
AUTO_LOAD = ["uart", "usb_host", "bytebuffer", "socket"]
|
||||
AUTO_LOAD = ["uart", "usb_host", "bytebuffer"]
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
|
||||
usb_uart_ns = cg.esphome_ns.namespace("usb_uart")
|
||||
@@ -117,10 +116,6 @@ CONFIG_SCHEMA = cv.ensure_list(
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
# Enable wake_loop_threadsafe for low-latency USB data processing
|
||||
# The USB task queues data events that need immediate processing
|
||||
socket.require_wake_loop_threadsafe()
|
||||
|
||||
for device in config:
|
||||
var = await register_usb_client(device)
|
||||
for index, channel in enumerate(device[CONF_CHANNELS]):
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4)
|
||||
#include "usb_uart.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/components/uart/uart_debugger.h"
|
||||
|
||||
#include <cinttypes>
|
||||
@@ -263,11 +262,6 @@ void USBUartComponent::start_input(USBUartChannel *channel) {
|
||||
// Push to lock-free queue for main loop processing
|
||||
// Push always succeeds because pool size == queue size
|
||||
this->usb_data_queue_.push(chunk);
|
||||
|
||||
// Wake main loop immediately to process USB data instead of waiting for select() timeout
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
|
||||
App.wake_loop_threadsafe();
|
||||
#endif
|
||||
}
|
||||
|
||||
// On success, restart input immediately from USB task for performance
|
||||
|
||||
@@ -301,7 +301,12 @@ void WebServer::setup() {
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
if (logger::global_logger != nullptr && this->expose_log_) {
|
||||
logger::global_logger->add_log_listener(this);
|
||||
logger::global_logger->add_on_log_callback(
|
||||
// logs are not deferred, the memory overhead would be too large
|
||||
[this](int level, const char *tag, const char *message, size_t message_len) {
|
||||
(void) message_len;
|
||||
this->events_.try_send_nodefer(message, "log", millis());
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -317,16 +322,6 @@ void WebServer::setup() {
|
||||
this->set_interval(10000, [this]() { this->events_.try_send_nodefer("", "ping", millis(), 30000); });
|
||||
}
|
||||
void WebServer::loop() { this->events_.loop(); }
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
void WebServer::on_log(uint8_t level, const char *tag, const char *message, size_t message_len) {
|
||||
(void) level;
|
||||
(void) tag;
|
||||
(void) message_len;
|
||||
this->events_.try_send_nodefer(message, "log", millis());
|
||||
}
|
||||
#endif
|
||||
|
||||
void WebServer::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Web Server:\n"
|
||||
|
||||
@@ -7,9 +7,6 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/controller.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#ifdef USE_LOGGER
|
||||
#include "esphome/components/logger/logger.h"
|
||||
#endif
|
||||
|
||||
#include <functional>
|
||||
#include <list>
|
||||
@@ -173,14 +170,7 @@ class DeferredUpdateEventSourceList : public std::list<DeferredUpdateEventSource
|
||||
* under the '/light/...', '/sensor/...', ... URLs. A full documentation for this API
|
||||
* can be found under https://esphome.io/web-api/index.html.
|
||||
*/
|
||||
class WebServer : public Controller,
|
||||
public Component,
|
||||
public AsyncWebHandler
|
||||
#ifdef USE_LOGGER
|
||||
,
|
||||
public logger::LogListener
|
||||
#endif
|
||||
{
|
||||
class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
#if !defined(USE_ESP32) && defined(USE_ARDUINO)
|
||||
friend class DeferredUpdateEventSourceList;
|
||||
#endif
|
||||
@@ -240,10 +230,6 @@ class WebServer : public Controller,
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override;
|
||||
#endif
|
||||
|
||||
/// MQTT setup priority.
|
||||
float get_setup_priority() const override;
|
||||
|
||||
|
||||
@@ -608,7 +608,7 @@ async def wifi_disable_to_code(config, action_id, template_arg, args):
|
||||
|
||||
KEEP_SCAN_RESULTS_KEY = "wifi_keep_scan_results"
|
||||
RUNTIME_POWER_SAVE_KEY = "wifi_runtime_power_save"
|
||||
WIFI_LISTENERS_KEY = "wifi_listeners"
|
||||
WIFI_CALLBACKS_KEY = "wifi_callbacks"
|
||||
|
||||
|
||||
def request_wifi_scan_results():
|
||||
@@ -634,15 +634,15 @@ def enable_runtime_power_save_control():
|
||||
CORE.data[RUNTIME_POWER_SAVE_KEY] = True
|
||||
|
||||
|
||||
def request_wifi_listeners() -> None:
|
||||
"""Request that WiFi state listeners be compiled in.
|
||||
def request_wifi_callbacks() -> None:
|
||||
"""Request that WiFi callbacks be compiled in.
|
||||
|
||||
Components that need to be notified about WiFi state changes (IP address changes,
|
||||
scan results, connection state) should call this function during their code generation.
|
||||
This enables the add_ip_state_listener(), add_scan_results_listener(),
|
||||
and add_connect_state_listener() APIs.
|
||||
This enables the add_on_ip_state_callback(), add_on_wifi_scan_state_callback(),
|
||||
and add_on_wifi_connect_state_callback() APIs.
|
||||
"""
|
||||
CORE.data[WIFI_LISTENERS_KEY] = True
|
||||
CORE.data[WIFI_CALLBACKS_KEY] = True
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.FINAL)
|
||||
@@ -654,8 +654,8 @@ async def final_step():
|
||||
)
|
||||
if CORE.data.get(RUNTIME_POWER_SAVE_KEY, False):
|
||||
cg.add_define("USE_WIFI_RUNTIME_POWER_SAVE")
|
||||
if CORE.data.get(WIFI_LISTENERS_KEY, False):
|
||||
cg.add_define("USE_WIFI_LISTENERS")
|
||||
if CORE.data.get(WIFI_CALLBACKS_KEY, False):
|
||||
cg.add_define("USE_WIFI_CALLBACKS")
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
|
||||
@@ -1612,20 +1612,6 @@ void WiFiComponent::retry_connect() {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_RP2040
|
||||
// RP2040's mDNS library (LEAmDNS) relies on LwipIntf::stateUpCB() to restart
|
||||
// mDNS when the network interface reconnects. However, this callback is disabled
|
||||
// in the arduino-pico framework. As a workaround, we block component setup until
|
||||
// WiFi is connected, ensuring mDNS.begin() is called with an active connection.
|
||||
|
||||
bool WiFiComponent::can_proceed() {
|
||||
if (!this->has_sta() || this->state_ == WIFI_COMPONENT_STATE_DISABLED || this->ap_setup_) {
|
||||
return true;
|
||||
}
|
||||
return this->is_connected();
|
||||
}
|
||||
#endif
|
||||
|
||||
void WiFiComponent::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
|
||||
bool WiFiComponent::is_connected() {
|
||||
return this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTED &&
|
||||
|
||||
@@ -242,37 +242,6 @@ enum WifiMinAuthMode : uint8_t {
|
||||
struct IDFWiFiEvent;
|
||||
#endif
|
||||
|
||||
/** Listener interface for WiFi IP state changes.
|
||||
*
|
||||
* Components can implement this interface to receive IP address updates
|
||||
* without the overhead of std::function callbacks.
|
||||
*/
|
||||
class WiFiIPStateListener {
|
||||
public:
|
||||
virtual void on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1,
|
||||
const network::IPAddress &dns2) = 0;
|
||||
};
|
||||
|
||||
/** Listener interface for WiFi scan results.
|
||||
*
|
||||
* Components can implement this interface to receive scan results
|
||||
* without the overhead of std::function callbacks.
|
||||
*/
|
||||
class WiFiScanResultsListener {
|
||||
public:
|
||||
virtual void on_wifi_scan_results(const wifi_scan_vector_t<WiFiScanResult> &results) = 0;
|
||||
};
|
||||
|
||||
/** Listener interface for WiFi connection state changes.
|
||||
*
|
||||
* Components can implement this interface to receive connection updates
|
||||
* without the overhead of std::function callbacks.
|
||||
*/
|
||||
class WiFiConnectStateListener {
|
||||
public:
|
||||
virtual void on_wifi_connect_state(const std::string &ssid, const bssid_t &bssid) = 0;
|
||||
};
|
||||
|
||||
/// This component is responsible for managing the ESP WiFi interface.
|
||||
class WiFiComponent : public Component {
|
||||
public:
|
||||
@@ -315,10 +284,6 @@ class WiFiComponent : public Component {
|
||||
|
||||
void retry_connect();
|
||||
|
||||
#ifdef USE_RP2040
|
||||
bool can_proceed() override;
|
||||
#endif
|
||||
|
||||
void set_reboot_timeout(uint32_t reboot_timeout);
|
||||
|
||||
bool is_connected();
|
||||
@@ -404,22 +369,26 @@ class WiFiComponent : public Component {
|
||||
|
||||
int32_t get_wifi_channel();
|
||||
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
/** Add a listener for IP state changes.
|
||||
* Listener receives: IP addresses, DNS address 1, DNS address 2
|
||||
*/
|
||||
void add_ip_state_listener(WiFiIPStateListener *listener) { this->ip_state_listeners_.push_back(listener); }
|
||||
/// Add a listener for WiFi scan results
|
||||
void add_scan_results_listener(WiFiScanResultsListener *listener) {
|
||||
this->scan_results_listeners_.push_back(listener);
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
/// Add a callback that will be called on configuration changes (IP change, SSID change, etc.)
|
||||
/// @param callback The callback to be called; template arguments are:
|
||||
/// - IP addresses
|
||||
/// - DNS address 1
|
||||
/// - DNS address 2
|
||||
void add_on_ip_state_callback(
|
||||
std::function<void(network::IPAddresses, network::IPAddress, network::IPAddress)> &&callback) {
|
||||
this->ip_state_callback_.add(std::move(callback));
|
||||
}
|
||||
/** Add a listener for WiFi connection state changes.
|
||||
* Listener receives: SSID, BSSID
|
||||
*/
|
||||
void add_connect_state_listener(WiFiConnectStateListener *listener) {
|
||||
this->connect_state_listeners_.push_back(listener);
|
||||
/// - Wi-Fi scan results
|
||||
void add_on_wifi_scan_state_callback(std::function<void(wifi_scan_vector_t<WiFiScanResult> &)> &&callback) {
|
||||
this->wifi_scan_state_callback_.add(std::move(callback));
|
||||
}
|
||||
#endif // USE_WIFI_LISTENERS
|
||||
/// - Wi-Fi SSID
|
||||
/// - Wi-Fi BSSID
|
||||
void add_on_wifi_connect_state_callback(std::function<void(std::string, wifi::bssid_t)> &&callback) {
|
||||
this->wifi_connect_state_callback_.add(std::move(callback));
|
||||
}
|
||||
#endif // USE_WIFI_CALLBACKS
|
||||
|
||||
#ifdef USE_WIFI_RUNTIME_POWER_SAVE
|
||||
/** Request high-performance mode (no power saving) for improved WiFi latency.
|
||||
@@ -577,11 +546,11 @@ class WiFiComponent : public Component {
|
||||
WiFiAP ap_;
|
||||
#endif
|
||||
optional<float> output_power_;
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
std::vector<WiFiIPStateListener *> ip_state_listeners_;
|
||||
std::vector<WiFiScanResultsListener *> scan_results_listeners_;
|
||||
std::vector<WiFiConnectStateListener *> connect_state_listeners_;
|
||||
#endif // USE_WIFI_LISTENERS
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
CallbackManager<void(network::IPAddresses, network::IPAddress, network::IPAddress)> ip_state_callback_;
|
||||
CallbackManager<void(wifi_scan_vector_t<WiFiScanResult> &)> wifi_scan_state_callback_;
|
||||
CallbackManager<void(std::string, wifi::bssid_t)> wifi_connect_state_callback_;
|
||||
#endif // USE_WIFI_CALLBACKS
|
||||
ESPPreferenceObject pref_;
|
||||
#ifdef USE_WIFI_FAST_CONNECT
|
||||
ESPPreferenceObject fast_connect_pref_;
|
||||
|
||||
@@ -513,10 +513,9 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
ESP_LOGV(TAG, "Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_address_pretty(it.bssid).c_str(),
|
||||
it.channel);
|
||||
s_sta_connected = true;
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : global_wifi_component->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(global_wifi_component->wifi_ssid(), global_wifi_component->wifi_bssid());
|
||||
}
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
global_wifi_component->wifi_connect_state_callback_.call(global_wifi_component->wifi_ssid(),
|
||||
global_wifi_component->wifi_bssid());
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -537,10 +536,8 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
}
|
||||
s_sta_connected = false;
|
||||
s_sta_connecting = false;
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : global_wifi_component->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0}));
|
||||
}
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
global_wifi_component->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0}));
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -564,11 +561,10 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
ESP_LOGV(TAG, "static_ip=%s gateway=%s netmask=%s", format_ip_addr(it.ip).c_str(), format_ip_addr(it.gw).c_str(),
|
||||
format_ip_addr(it.mask).c_str());
|
||||
s_sta_got_ip = true;
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : global_wifi_component->ip_state_listeners_) {
|
||||
listener->on_ip_state(global_wifi_component->wifi_sta_ip_addresses(), global_wifi_component->get_dns_address(0),
|
||||
global_wifi_component->get_dns_address(1));
|
||||
}
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
global_wifi_component->ip_state_callback_.call(global_wifi_component->wifi_sta_ip_addresses(),
|
||||
global_wifi_component->get_dns_address(0),
|
||||
global_wifi_component->get_dns_address(1));
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -744,10 +740,8 @@ void WiFiComponent::wifi_scan_done_callback_(void *arg, STATUS status) {
|
||||
it->is_hidden != 0);
|
||||
}
|
||||
this->scan_done_ = true;
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : global_wifi_component->scan_results_listeners_) {
|
||||
listener->on_wifi_scan_results(global_wifi_component->scan_result_);
|
||||
}
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
global_wifi_component->wifi_scan_state_callback_.call(global_wifi_component->scan_result_);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -884,9 +878,10 @@ network::IPAddress WiFiComponent::wifi_soft_ap_ip() {
|
||||
|
||||
bssid_t WiFiComponent::wifi_bssid() {
|
||||
bssid_t bssid{};
|
||||
struct station_config conf {};
|
||||
if (wifi_station_get_config(&conf)) {
|
||||
std::copy_n(conf.bssid, bssid.size(), bssid.begin());
|
||||
uint8_t *raw_bssid = WiFi.BSSID();
|
||||
if (raw_bssid != nullptr) {
|
||||
for (size_t i = 0; i < bssid.size(); i++)
|
||||
bssid[i] = raw_bssid[i];
|
||||
}
|
||||
return bssid;
|
||||
}
|
||||
|
||||
@@ -727,10 +727,8 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
|
||||
format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
|
||||
s_sta_connected = true;
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid());
|
||||
}
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->wifi_connect_state_callback_.call(this->wifi_ssid(), this->wifi_bssid());
|
||||
#endif
|
||||
|
||||
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
@@ -755,10 +753,8 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
s_sta_connected = false;
|
||||
s_sta_connecting = false;
|
||||
error_from_callback_ = true;
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0}));
|
||||
}
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0}));
|
||||
#endif
|
||||
|
||||
} else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_GOT_IP) {
|
||||
@@ -768,10 +764,8 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
ESP_LOGV(TAG, "static_ip=" IPSTR " gateway=" IPSTR, IP2STR(&it.ip_info.ip), IP2STR(&it.ip_info.gw));
|
||||
this->got_ipv4_address_ = true;
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->ip_state_listeners_) {
|
||||
listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
}
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
#endif
|
||||
|
||||
#if USE_NETWORK_IPV6
|
||||
@@ -779,10 +773,8 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
const auto &it = data->data.ip_got_ip6;
|
||||
ESP_LOGV(TAG, "IPv6 address=" IPV6STR, IPV62STR(it.ip6_info.ip));
|
||||
this->num_ipv6_addresses_++;
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->ip_state_listeners_) {
|
||||
listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
}
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
#endif
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
@@ -823,10 +815,8 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
scan_result_.emplace_back(bssid, ssid, record.primary, record.rssi, record.authmode != WIFI_AUTH_OPEN,
|
||||
ssid.empty());
|
||||
}
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->scan_results_listeners_) {
|
||||
listener->on_wifi_scan_results(this->scan_result_);
|
||||
}
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->wifi_scan_state_callback_.call(this->scan_result_);
|
||||
#endif
|
||||
|
||||
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_START) {
|
||||
|
||||
@@ -287,10 +287,8 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
||||
buf[it.ssid_len] = '\0';
|
||||
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
|
||||
format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid());
|
||||
}
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->wifi_connect_state_callback_.call(this->wifi_ssid(), this->wifi_bssid());
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -317,10 +315,8 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
||||
}
|
||||
|
||||
s_sta_connecting = false;
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0}));
|
||||
}
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0}));
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -343,20 +339,16 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
||||
ESP_LOGV(TAG, "static_ip=%s gateway=%s", format_ip4_addr(WiFi.localIP()).c_str(),
|
||||
format_ip4_addr(WiFi.gatewayIP()).c_str());
|
||||
s_sta_connecting = false;
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->ip_state_listeners_) {
|
||||
listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
}
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6: {
|
||||
// auto it = info.got_ip.ip_info;
|
||||
ESP_LOGV(TAG, "Got IPv6");
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->ip_state_listeners_) {
|
||||
listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
}
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -451,10 +443,8 @@ void WiFiComponent::wifi_scan_done_callback_() {
|
||||
}
|
||||
WiFi.scanDelete();
|
||||
this->scan_done_ = true;
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->scan_results_listeners_) {
|
||||
listener->on_wifi_scan_results(this->scan_result_);
|
||||
}
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->wifi_scan_state_callback_.call(this->scan_result_);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -225,10 +225,8 @@ void WiFiComponent::wifi_loop_() {
|
||||
if (this->state_ == WIFI_COMPONENT_STATE_STA_SCANNING && !cyw43_wifi_scan_active(&cyw43_state)) {
|
||||
this->scan_done_ = true;
|
||||
ESP_LOGV(TAG, "Scan done");
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->scan_results_listeners_) {
|
||||
listener->on_wifi_scan_results(this->scan_result_);
|
||||
}
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->wifi_scan_state_callback_.call(this->scan_result_);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -243,20 +241,16 @@ void WiFiComponent::wifi_loop_() {
|
||||
// Just connected
|
||||
s_sta_was_connected = true;
|
||||
ESP_LOGV(TAG, "Connected");
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid());
|
||||
}
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->wifi_connect_state_callback_.call(this->wifi_ssid(), this->wifi_bssid());
|
||||
#endif
|
||||
} else if (!is_connected && s_sta_was_connected) {
|
||||
// Just disconnected
|
||||
s_sta_was_connected = false;
|
||||
s_sta_had_ip = false;
|
||||
ESP_LOGV(TAG, "Disconnected");
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0}));
|
||||
}
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0}));
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -273,10 +267,8 @@ void WiFiComponent::wifi_loop_() {
|
||||
// Just got IP address
|
||||
s_sta_had_ip = true;
|
||||
ESP_LOGV(TAG, "Got IP address");
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->ip_state_listeners_) {
|
||||
listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
}
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
# Keys that require WiFi listeners
|
||||
# Keys that require WiFi callbacks
|
||||
_NETWORK_INFO_KEYS = {
|
||||
CONF_SSID,
|
||||
CONF_BSSID,
|
||||
@@ -79,9 +79,9 @@ async def setup_conf(config, key):
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
# Request WiFi listeners for any sensor that needs them
|
||||
# Request WiFi callbacks for any sensor that needs them
|
||||
if _NETWORK_INFO_KEYS.intersection(config):
|
||||
wifi.request_wifi_listeners()
|
||||
wifi.request_wifi_callbacks()
|
||||
|
||||
await setup_conf(config, CONF_SSID)
|
||||
await setup_conf(config, CONF_BSSID)
|
||||
|
||||
@@ -12,12 +12,16 @@ static constexpr size_t MAX_STATE_LENGTH = 255;
|
||||
* IPAddressWiFiInfo
|
||||
*******************/
|
||||
|
||||
void IPAddressWiFiInfo::setup() { wifi::global_wifi_component->add_ip_state_listener(this); }
|
||||
void IPAddressWiFiInfo::setup() {
|
||||
wifi::global_wifi_component->add_on_ip_state_callback(
|
||||
[this](const network::IPAddresses &ips, const network::IPAddress &dns1_ip, const network::IPAddress &dns2_ip) {
|
||||
this->state_callback_(ips);
|
||||
});
|
||||
}
|
||||
|
||||
void IPAddressWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "IP Address", this); }
|
||||
|
||||
void IPAddressWiFiInfo::on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1,
|
||||
const network::IPAddress &dns2) {
|
||||
void IPAddressWiFiInfo::state_callback_(const network::IPAddresses &ips) {
|
||||
this->publish_state(ips[0].str());
|
||||
uint8_t sensor = 0;
|
||||
for (const auto &ip : ips) {
|
||||
@@ -34,13 +38,17 @@ void IPAddressWiFiInfo::on_ip_state(const network::IPAddresses &ips, const netwo
|
||||
* DNSAddressWifiInfo
|
||||
********************/
|
||||
|
||||
void DNSAddressWifiInfo::setup() { wifi::global_wifi_component->add_ip_state_listener(this); }
|
||||
void DNSAddressWifiInfo::setup() {
|
||||
wifi::global_wifi_component->add_on_ip_state_callback(
|
||||
[this](const network::IPAddresses &ips, const network::IPAddress &dns1_ip, const network::IPAddress &dns2_ip) {
|
||||
this->state_callback_(dns1_ip, dns2_ip);
|
||||
});
|
||||
}
|
||||
|
||||
void DNSAddressWifiInfo::dump_config() { LOG_TEXT_SENSOR("", "DNS Address", this); }
|
||||
|
||||
void DNSAddressWifiInfo::on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1,
|
||||
const network::IPAddress &dns2) {
|
||||
std::string dns_results = dns1.str() + " " + dns2.str();
|
||||
void DNSAddressWifiInfo::state_callback_(const network::IPAddress &dns1_ip, const network::IPAddress &dns2_ip) {
|
||||
std::string dns_results = dns1_ip.str() + " " + dns2_ip.str();
|
||||
this->publish_state(dns_results);
|
||||
}
|
||||
|
||||
@@ -48,11 +56,14 @@ void DNSAddressWifiInfo::on_ip_state(const network::IPAddresses &ips, const netw
|
||||
* ScanResultsWiFiInfo
|
||||
*********************/
|
||||
|
||||
void ScanResultsWiFiInfo::setup() { wifi::global_wifi_component->add_scan_results_listener(this); }
|
||||
void ScanResultsWiFiInfo::setup() {
|
||||
wifi::global_wifi_component->add_on_wifi_scan_state_callback(
|
||||
[this](const wifi::wifi_scan_vector_t<wifi::WiFiScanResult> &results) { this->state_callback_(results); });
|
||||
}
|
||||
|
||||
void ScanResultsWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "Scan Results", this); }
|
||||
|
||||
void ScanResultsWiFiInfo::on_wifi_scan_results(const wifi::wifi_scan_vector_t<wifi::WiFiScanResult> &results) {
|
||||
void ScanResultsWiFiInfo::state_callback_(const wifi::wifi_scan_vector_t<wifi::WiFiScanResult> &results) {
|
||||
std::string scan_results;
|
||||
for (const auto &scan : results) {
|
||||
if (scan.get_is_hidden())
|
||||
@@ -74,30 +85,33 @@ void ScanResultsWiFiInfo::on_wifi_scan_results(const wifi::wifi_scan_vector_t<wi
|
||||
* SSIDWiFiInfo
|
||||
**************/
|
||||
|
||||
void SSIDWiFiInfo::setup() { wifi::global_wifi_component->add_connect_state_listener(this); }
|
||||
void SSIDWiFiInfo::setup() {
|
||||
wifi::global_wifi_component->add_on_wifi_connect_state_callback(
|
||||
[this](const std::string &ssid, const wifi::bssid_t &bssid) { this->state_callback_(ssid); });
|
||||
}
|
||||
|
||||
void SSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "SSID", this); }
|
||||
|
||||
void SSIDWiFiInfo::on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) {
|
||||
this->publish_state(ssid);
|
||||
}
|
||||
void SSIDWiFiInfo::state_callback_(const std::string &ssid) { this->publish_state(ssid); }
|
||||
|
||||
/****************
|
||||
* BSSIDWiFiInfo
|
||||
***************/
|
||||
|
||||
void BSSIDWiFiInfo::setup() { wifi::global_wifi_component->add_connect_state_listener(this); }
|
||||
void BSSIDWiFiInfo::setup() {
|
||||
wifi::global_wifi_component->add_on_wifi_connect_state_callback(
|
||||
[this](const std::string &ssid, const wifi::bssid_t &bssid) { this->state_callback_(bssid); });
|
||||
}
|
||||
|
||||
void BSSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "BSSID", this); }
|
||||
|
||||
void BSSIDWiFiInfo::on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) {
|
||||
void BSSIDWiFiInfo::state_callback_(const wifi::bssid_t &bssid) {
|
||||
char buf[18] = "unknown";
|
||||
if (mac_address_is_valid(bssid.data())) {
|
||||
format_mac_addr_upper(bssid.data(), buf);
|
||||
}
|
||||
this->publish_state(buf);
|
||||
}
|
||||
|
||||
/*********************
|
||||
* MacAddressWifiInfo
|
||||
********************/
|
||||
|
||||
@@ -9,61 +9,55 @@
|
||||
|
||||
namespace esphome::wifi_info {
|
||||
|
||||
class IPAddressWiFiInfo final : public Component, public text_sensor::TextSensor, public wifi::WiFiIPStateListener {
|
||||
class IPAddressWiFiInfo : public Component, public text_sensor::TextSensor {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void add_ip_sensors(uint8_t index, text_sensor::TextSensor *s) { this->ip_sensors_[index] = s; }
|
||||
|
||||
// WiFiIPStateListener interface
|
||||
void on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1,
|
||||
const network::IPAddress &dns2) override;
|
||||
|
||||
protected:
|
||||
void state_callback_(const network::IPAddresses &ips);
|
||||
std::array<text_sensor::TextSensor *, 5> ip_sensors_;
|
||||
};
|
||||
|
||||
class DNSAddressWifiInfo final : public Component, public text_sensor::TextSensor, public wifi::WiFiIPStateListener {
|
||||
class DNSAddressWifiInfo : public Component, public text_sensor::TextSensor {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
// WiFiIPStateListener interface
|
||||
void on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1,
|
||||
const network::IPAddress &dns2) override;
|
||||
protected:
|
||||
void state_callback_(const network::IPAddress &dns1_ip, const network::IPAddress &dns2_ip);
|
||||
};
|
||||
|
||||
class ScanResultsWiFiInfo final : public Component,
|
||||
public text_sensor::TextSensor,
|
||||
public wifi::WiFiScanResultsListener {
|
||||
class ScanResultsWiFiInfo : public Component, public text_sensor::TextSensor {
|
||||
public:
|
||||
void setup() override;
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
void dump_config() override;
|
||||
|
||||
// WiFiScanResultsListener interface
|
||||
void on_wifi_scan_results(const wifi::wifi_scan_vector_t<wifi::WiFiScanResult> &results) override;
|
||||
protected:
|
||||
void state_callback_(const wifi::wifi_scan_vector_t<wifi::WiFiScanResult> &results);
|
||||
};
|
||||
|
||||
class SSIDWiFiInfo final : public Component, public text_sensor::TextSensor, public wifi::WiFiConnectStateListener {
|
||||
class SSIDWiFiInfo : public Component, public text_sensor::TextSensor {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
// WiFiConnectStateListener interface
|
||||
void on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) override;
|
||||
protected:
|
||||
void state_callback_(const std::string &ssid);
|
||||
};
|
||||
|
||||
class BSSIDWiFiInfo final : public Component, public text_sensor::TextSensor, public wifi::WiFiConnectStateListener {
|
||||
class BSSIDWiFiInfo : public Component, public text_sensor::TextSensor {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
// WiFiConnectStateListener interface
|
||||
void on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) override;
|
||||
protected:
|
||||
void state_callback_(const wifi::bssid_t &bssid);
|
||||
};
|
||||
|
||||
class MacAddressWifiInfo final : public Component, public text_sensor::TextSensor {
|
||||
class MacAddressWifiInfo : public Component, public text_sensor::TextSensor {
|
||||
public:
|
||||
void setup() override {
|
||||
char mac_s[18];
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
#define USE_LIGHT
|
||||
#define USE_LOCK
|
||||
#define USE_LOGGER
|
||||
#define USE_LOGGER_LEVEL_LISTENERS
|
||||
#define USE_LOGGER_RUNTIME_TAG_LEVELS
|
||||
#define USE_LVGL
|
||||
#define USE_LVGL_ANIMIMG
|
||||
@@ -211,7 +210,7 @@
|
||||
#define USE_WEBSERVER_SORTING
|
||||
#define USE_WIFI_11KV_SUPPORT
|
||||
#define USE_WIFI_FAST_CONNECT
|
||||
#define USE_WIFI_LISTENERS
|
||||
#define USE_WIFI_CALLBACKS
|
||||
#define USE_WIFI_RUNTIME_POWER_SAVE
|
||||
#define USB_HOST_MAX_REQUESTS 16
|
||||
|
||||
|
||||
@@ -359,7 +359,8 @@ void HOT Scheduler::call(uint32_t now) {
|
||||
std::unique_ptr<SchedulerItem> item;
|
||||
{
|
||||
LockGuard guard{this->lock_};
|
||||
item = this->pop_raw_locked_();
|
||||
item = std::move(this->items_[0]);
|
||||
this->pop_raw_();
|
||||
}
|
||||
|
||||
const char *name = item->get_name();
|
||||
@@ -400,7 +401,7 @@ void HOT Scheduler::call(uint32_t now) {
|
||||
// Don't run on failed components
|
||||
if (item->component != nullptr && item->component->is_failed()) {
|
||||
LockGuard guard{this->lock_};
|
||||
this->recycle_item_(this->pop_raw_locked_());
|
||||
this->pop_raw_();
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -413,7 +414,7 @@ void HOT Scheduler::call(uint32_t now) {
|
||||
{
|
||||
LockGuard guard{this->lock_};
|
||||
if (is_item_removed_(item.get())) {
|
||||
this->recycle_item_(this->pop_raw_locked_());
|
||||
this->pop_raw_();
|
||||
this->to_remove_--;
|
||||
continue;
|
||||
}
|
||||
@@ -422,7 +423,7 @@ void HOT Scheduler::call(uint32_t now) {
|
||||
// Single-threaded or multi-threaded with atomics: can check without lock
|
||||
if (is_item_removed_(item.get())) {
|
||||
LockGuard guard{this->lock_};
|
||||
this->recycle_item_(this->pop_raw_locked_());
|
||||
this->pop_raw_();
|
||||
this->to_remove_--;
|
||||
continue;
|
||||
}
|
||||
@@ -442,14 +443,14 @@ void HOT Scheduler::call(uint32_t now) {
|
||||
|
||||
LockGuard guard{this->lock_};
|
||||
|
||||
auto executed_item = std::move(this->items_[0]);
|
||||
// Only pop after function call, this ensures we were reachable
|
||||
// during the function call and know if we were cancelled.
|
||||
auto executed_item = this->pop_raw_locked_();
|
||||
this->pop_raw_();
|
||||
|
||||
if (executed_item->remove) {
|
||||
// We were removed/cancelled in the function call, recycle and continue
|
||||
// We were removed/cancelled in the function call, stop
|
||||
this->to_remove_--;
|
||||
this->recycle_item_(std::move(executed_item));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -496,7 +497,7 @@ size_t HOT Scheduler::cleanup_() {
|
||||
return this->items_.size();
|
||||
|
||||
// We must hold the lock for the entire cleanup operation because:
|
||||
// 1. We're modifying items_ (via pop_raw_locked_) which requires exclusive access
|
||||
// 1. We're modifying items_ (via pop_raw_) which requires exclusive access
|
||||
// 2. We're decrementing to_remove_ which is also modified by other threads
|
||||
// (though all modifications are already under lock)
|
||||
// 3. Other threads read items_ when searching for items to cancel in cancel_item_locked_()
|
||||
@@ -509,18 +510,17 @@ size_t HOT Scheduler::cleanup_() {
|
||||
if (!item->remove)
|
||||
break;
|
||||
this->to_remove_--;
|
||||
this->recycle_item_(this->pop_raw_locked_());
|
||||
this->pop_raw_();
|
||||
}
|
||||
return this->items_.size();
|
||||
}
|
||||
std::unique_ptr<Scheduler::SchedulerItem> HOT Scheduler::pop_raw_locked_() {
|
||||
void HOT Scheduler::pop_raw_() {
|
||||
std::pop_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
|
||||
|
||||
// Move the item out before popping - this is the item that was at the front of the heap
|
||||
auto item = std::move(this->items_.back());
|
||||
// Instead of destroying, recycle the item
|
||||
this->recycle_item_(std::move(this->items_.back()));
|
||||
|
||||
this->items_.pop_back();
|
||||
return item;
|
||||
}
|
||||
|
||||
// Helper to execute a scheduler item
|
||||
|
||||
@@ -219,9 +219,7 @@ class Scheduler {
|
||||
// Returns the number of items remaining after cleanup
|
||||
// IMPORTANT: This method should only be called from the main thread (loop task).
|
||||
size_t cleanup_();
|
||||
// Remove and return the front item from the heap
|
||||
// IMPORTANT: Caller must hold the scheduler lock before calling this function.
|
||||
std::unique_ptr<SchedulerItem> pop_raw_locked_();
|
||||
void pop_raw_();
|
||||
|
||||
private:
|
||||
// Helper to cancel items by name - must be called with lock held
|
||||
|
||||
@@ -2769,8 +2769,8 @@ static const char *const TAG = "api.service";
|
||||
cases = list(RECEIVE_CASES.items())
|
||||
cases.sort()
|
||||
hpp += " protected:\n"
|
||||
hpp += " void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;\n"
|
||||
out = f"void {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {{\n"
|
||||
hpp += " void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;\n"
|
||||
out = f"void {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {{\n"
|
||||
out += " switch (msg_type) {\n"
|
||||
for i, (case, ifdef, message_name) in cases:
|
||||
if ifdef is not None:
|
||||
@@ -2878,9 +2878,9 @@ static const char *const TAG = "api.service";
|
||||
result += "#endif\n"
|
||||
return result
|
||||
|
||||
hpp_protected += " void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;\n"
|
||||
hpp_protected += " void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;\n"
|
||||
|
||||
cpp += f"\nvoid {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {{\n"
|
||||
cpp += f"\nvoid {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {{\n"
|
||||
cpp += " // Check authentication/connection requirements for messages\n"
|
||||
cpp += " switch (msg_type) {\n"
|
||||
|
||||
|
||||
@@ -4,10 +4,6 @@ esp32:
|
||||
cpu_frequency: 400MHz
|
||||
framework:
|
||||
type: esp-idf
|
||||
components:
|
||||
- espressif/mdns^1.8.2
|
||||
- name: espressif/esp_hosted
|
||||
ref: 2.6.6
|
||||
advanced:
|
||||
enable_idf_experimental_features: yes
|
||||
|
||||
|
||||
4
tests/components/seeed_mr24hpc1/test.esp8266-ard.yaml
Normal file
4
tests/components/seeed_mr24hpc1/test.esp8266-ard.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,30 +0,0 @@
|
||||
substitutions:
|
||||
x: 10
|
||||
y: 20
|
||||
z: 30
|
||||
values_from_repo1_main:
|
||||
- package_name: package1
|
||||
x: 3
|
||||
y: 4
|
||||
z: 5
|
||||
volume: 60
|
||||
- package_name: package2
|
||||
x: 6
|
||||
y: 7
|
||||
z: 8
|
||||
volume: 336
|
||||
- package_name: default
|
||||
x: 10
|
||||
y: 20
|
||||
z: 5
|
||||
volume: 1000
|
||||
- package_name: package4_from_repo2
|
||||
x: 9
|
||||
y: 10
|
||||
z: 11
|
||||
volume: 990
|
||||
- package_name: default
|
||||
x: 10
|
||||
y: 20
|
||||
z: 5
|
||||
volume: 1000
|
||||
@@ -1,43 +0,0 @@
|
||||
substitutions:
|
||||
x: 10
|
||||
y: 20
|
||||
z: 30
|
||||
packages:
|
||||
package1:
|
||||
url: https://github.com/esphome/repo1
|
||||
files:
|
||||
- path: file1.yaml
|
||||
vars:
|
||||
package_name: package1
|
||||
x: 3
|
||||
y: 4
|
||||
ref: main
|
||||
package2: !include # a package that just includes the given remote package
|
||||
file: remote_package_proxy.yaml
|
||||
vars:
|
||||
url: https://github.com/esphome/repo1
|
||||
ref: main
|
||||
files:
|
||||
- path: file1.yaml
|
||||
vars:
|
||||
package_name: package2
|
||||
x: 6
|
||||
y: 7
|
||||
z: 8
|
||||
package3: github://esphome/repo1/file1.yaml@main # a package that uses the shorthand syntax
|
||||
package4: # include repo2, which itself includes repo1
|
||||
url: https://github.com/esphome/repo2
|
||||
files:
|
||||
- path: file2.yaml
|
||||
vars:
|
||||
package_name: package4
|
||||
a: 9
|
||||
b: 10
|
||||
c: 11
|
||||
ref: main
|
||||
package5: !include
|
||||
file: remote_package_shorthand.yaml
|
||||
vars:
|
||||
repo: repo1
|
||||
file: file1.yaml
|
||||
ref: main
|
||||
@@ -1,6 +0,0 @@
|
||||
# acts as a proxy to be able to include a remote package
|
||||
# in which the url/ref/files come from a substitution
|
||||
packages:
|
||||
- url: ${url}
|
||||
ref: ${ref}
|
||||
files: ${files}
|
||||
@@ -1,4 +0,0 @@
|
||||
# acts as a proxy to be able to include a remote package
|
||||
# in which the shorthand comes from a substitution
|
||||
packages:
|
||||
- github://esphome/${repo}/${file}@${ref}
|
||||
@@ -1,3 +0,0 @@
|
||||
This folder contains fake repos for remote packages testing
|
||||
These are used by `test_substitutions.py`.
|
||||
To add repos, create a folder and add its path to the `REMOTES` constant in `test_substitutions.py`.
|
||||
@@ -1,9 +0,0 @@
|
||||
defaults:
|
||||
z: 5
|
||||
package_name: default
|
||||
values_from_repo1_main:
|
||||
- package_name: ${package_name}
|
||||
x: ${x}
|
||||
y: ${y}
|
||||
z: ${z}
|
||||
volume: ${x*y*z}
|
||||
@@ -1,10 +0,0 @@
|
||||
packages:
|
||||
- url: https://github.com/esphome/repo1
|
||||
ref: main
|
||||
files:
|
||||
- path: file1.yaml
|
||||
vars:
|
||||
package_name: ${package_name}_from_repo2
|
||||
x: ${a}
|
||||
y: ${b}
|
||||
z: ${c}
|
||||
@@ -2,7 +2,6 @@ import glob
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
from esphome import config as config_module, yaml_util
|
||||
from esphome.components import substitutions
|
||||
@@ -85,41 +84,11 @@ def verify_database(value: Any, path: str = "") -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
# Mapping of (url, ref) to local test repository path under fixtures/substitutions
|
||||
REMOTES = {
|
||||
("https://github.com/esphome/repo1", "main"): "remotes/repo1/main",
|
||||
("https://github.com/esphome/repo2", "main"): "remotes/repo2/main",
|
||||
}
|
||||
|
||||
|
||||
@patch("esphome.git.clone_or_update")
|
||||
def test_substitutions_fixtures(mock_clone_or_update, fixture_path):
|
||||
def test_substitutions_fixtures(fixture_path):
|
||||
base_dir = fixture_path / "substitutions"
|
||||
sources = sorted(glob.glob(str(base_dir / "*.input.yaml")))
|
||||
assert sources, f"No input YAML files found in {base_dir}"
|
||||
|
||||
def fake_clone_or_update(
|
||||
*,
|
||||
url: str,
|
||||
ref: str | None = None,
|
||||
refresh=None,
|
||||
domain: str,
|
||||
username: str | None = None,
|
||||
password: str | None = None,
|
||||
submodules: list[str] | None = None,
|
||||
_recover_broken: bool = True,
|
||||
) -> tuple[Path, None]:
|
||||
path = REMOTES.get((url, ref))
|
||||
if path is None:
|
||||
path = REMOTES.get((url.rstrip(".git"), ref))
|
||||
if path is None:
|
||||
raise RuntimeError(
|
||||
f"Cannot find test repository for {url} @ {ref}. Check the REMOTES mapping in test_substitutions.py"
|
||||
)
|
||||
return base_dir / path, None
|
||||
|
||||
mock_clone_or_update.side_effect = fake_clone_or_update
|
||||
|
||||
failures = []
|
||||
for source_path in sources:
|
||||
source_path = Path(source_path)
|
||||
|
||||
Reference in New Issue
Block a user