mirror of
https://github.com/esphome/esphome.git
synced 2026-01-19 09:36:24 -07:00
Compare commits
27 Commits
filter-pla
...
speed_coun
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a30786b055 | ||
|
|
192abf95ce | ||
|
|
bc50be6053 | ||
|
|
ca599b25c2 | ||
|
|
2e55296640 | ||
|
|
d6ca01775e | ||
|
|
e15f3a08ae | ||
|
|
fb82362e9c | ||
|
|
26e979d3d5 | ||
|
|
60ffa0e52e | ||
|
|
e1ec6146c0 | ||
|
|
450065fdae | ||
|
|
71dc402a30 | ||
|
|
9bd148dfd1 | ||
|
|
50c1720c16 | ||
|
|
4c549798bc | ||
|
|
4115dd7222 | ||
|
|
d5e2543751 | ||
|
|
b4b34aee13 | ||
|
|
6645994700 | ||
|
|
ae140f52e3 | ||
|
|
46ae6d35a2 | ||
|
|
278f12fb99 | ||
|
|
acdcd56395 | ||
|
|
9289fc36f7 | ||
|
|
1fadd1227d | ||
|
|
91df0548ef |
@@ -169,8 +169,7 @@ void APIConnection::loop() {
|
||||
} else {
|
||||
this->last_traffic_ = now;
|
||||
// read a packet
|
||||
this->read_message(buffer.data_len, buffer.type,
|
||||
buffer.data_len > 0 ? &buffer.container[buffer.data_offset] : nullptr);
|
||||
this->read_message(buffer.data_len, buffer.type, buffer.data);
|
||||
if (this->flags_.remove)
|
||||
return;
|
||||
}
|
||||
@@ -195,6 +194,9 @@ 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,10 +554,8 @@ class APIConnection final : public APIServerConnection {
|
||||
std::vector<BatchItem> items;
|
||||
uint32_t batch_start_time{0};
|
||||
|
||||
DeferredBatch() {
|
||||
// Pre-allocate capacity for typical batch sizes to avoid reallocation
|
||||
items.reserve(8);
|
||||
}
|
||||
// No pre-allocation - log connections never use batching, and for
|
||||
// connections that do, buffers are released after initial sync anyway
|
||||
|
||||
// Add item to the batch
|
||||
void add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
|
||||
@@ -576,6 +574,15 @@ 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,10 +35,9 @@ struct ClientInfo;
|
||||
class ProtoWriteBuffer;
|
||||
|
||||
struct ReadPacketBuffer {
|
||||
std::vector<uint8_t> container;
|
||||
uint16_t type;
|
||||
uint16_t data_offset;
|
||||
const uint8_t *data; // Points directly into frame helper's rx_buf_ (valid until next read_packet call)
|
||||
uint16_t data_len;
|
||||
uint16_t type;
|
||||
};
|
||||
|
||||
// Packed packet info structure to minimize memory usage
|
||||
@@ -119,6 +118,22 @@ 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,8 +407,7 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
return APIError::BAD_DATA_PACKET;
|
||||
}
|
||||
|
||||
buffer->container = std::move(this->rx_buf_);
|
||||
buffer->data_offset = 4;
|
||||
buffer->data = msg_data + 4; // Skip 4-byte header (type + length)
|
||||
buffer->data_len = data_len;
|
||||
buffer->type = type;
|
||||
return APIError::OK;
|
||||
|
||||
@@ -210,8 +210,7 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
return aerr;
|
||||
}
|
||||
|
||||
buffer->container = std::move(this->rx_buf_);
|
||||
buffer->data_offset = 0;
|
||||
buffer->data = this->rx_buf_.data();
|
||||
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, uint8_t *msg_data) {
|
||||
void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, const 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, uint8_t *msg_data) {
|
||||
void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, const 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, uint8_t *msg_data) override;
|
||||
void read_message(uint32_t msg_size, uint32_t msg_type, const 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, uint8_t *msg_data) override;
|
||||
void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;
|
||||
};
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
@@ -101,19 +101,7 @@ void APIServer::setup() {
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
if (logger::global_logger != nullptr) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
logger::global_logger->add_log_listener(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -541,6 +529,21 @@ 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,6 +15,9 @@
|
||||
#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>
|
||||
@@ -27,7 +30,13 @@ struct SavedNoisePsk {
|
||||
} PACKED; // NOLINT
|
||||
#endif
|
||||
|
||||
class APIServer : public Component, public Controller {
|
||||
class APIServer : public Component,
|
||||
public Controller
|
||||
#ifdef USE_LOGGER
|
||||
,
|
||||
public logger::LogListener
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
APIServer();
|
||||
void setup() override;
|
||||
@@ -37,6 +46,9 @@ class APIServer : public Component, public Controller {
|
||||
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, uint8_t *msg_data) = 0;
|
||||
virtual void read_message(uint32_t msg_size, uint32_t msg_type, const 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,17 +87,21 @@ void BLENUS::setup() {
|
||||
global_ble_nus = this;
|
||||
#ifdef USE_LOGGER
|
||||
if (logger::global_logger != nullptr && this->expose_log_) {
|
||||
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);
|
||||
});
|
||||
logger::global_logger->add_log_listener(this);
|
||||
}
|
||||
|
||||
#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:");
|
||||
ESP_LOGCONFIG(TAG, " log: %s", YESNO(this->expose_log_));
|
||||
|
||||
@@ -2,12 +2,20 @@
|
||||
#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 {
|
||||
class BLENUS : public Component
|
||||
#ifdef USE_LOGGER
|
||||
,
|
||||
public logger::LogListener
|
||||
#endif
|
||||
{
|
||||
enum TxStatus {
|
||||
TX_DISABLED,
|
||||
TX_ENABLED,
|
||||
@@ -20,6 +28,9 @@ 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,11 +27,13 @@ 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_callback([this](esp32_ble_tracker::ScannerState state) {
|
||||
if (this->api_connection_ != nullptr) {
|
||||
this->send_bluetooth_scanner_state_(state);
|
||||
}
|
||||
});
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state) {
|
||||
|
||||
@@ -52,7 +52,9 @@ enum BluetoothProxySubscriptionFlag : uint32_t {
|
||||
SUBSCRIPTION_RAW_ADVERTISEMENTS = 1 << 0,
|
||||
};
|
||||
|
||||
class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||
class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener,
|
||||
public esp32_ble_tracker::BLEScannerStateListener,
|
||||
public Component {
|
||||
friend class BluetoothConnection; // Allow connection to update connections_free_response_
|
||||
public:
|
||||
BluetoothProxy();
|
||||
@@ -108,6 +110,9 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, publ
|
||||
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;
|
||||
|
||||
@@ -373,7 +373,9 @@ 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;
|
||||
this->scanner_state_callbacks_.call(state);
|
||||
for (auto *listener : this->scanner_state_listeners_) {
|
||||
listener->on_scanner_state(state);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
|
||||
@@ -180,6 +180,16 @@ 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);
|
||||
|
||||
@@ -264,8 +274,9 @@ class ESP32BLETracker : public Component,
|
||||
void gap_scan_event_handler(const BLEScanResult &scan_result) override;
|
||||
void ble_before_disabled_event_handler() override;
|
||||
|
||||
void add_scanner_state_callback(std::function<void(ScannerState)> &&callback) {
|
||||
this->scanner_state_callbacks_.add(std::move(callback));
|
||||
/// Add a listener for scanner state changes
|
||||
void add_scanner_state_listener(BLEScannerStateListener *listener) {
|
||||
this->scanner_state_listeners_.push_back(listener);
|
||||
}
|
||||
ScannerState get_scanner_state() const { return this->scanner_state_; }
|
||||
|
||||
@@ -322,14 +333,14 @@ class ESP32BLETracker : public Component,
|
||||
return counts;
|
||||
}
|
||||
|
||||
// Group 1: Large objects (12+ bytes) - vectors and callback manager
|
||||
// Group 1: Large objects (12+ bytes) - vectors
|
||||
#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
|
||||
CallbackManager<void(ScannerState)> scanner_state_callbacks_;
|
||||
std::vector<BLEScannerStateListener *> scanner_state_listeners_;
|
||||
#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,6 +10,7 @@
|
||||
|
||||
#include <esp_event.h>
|
||||
#include <esp_mac.h>
|
||||
#include <esp_netif.h>
|
||||
#include <esp_now.h>
|
||||
#include <esp_random.h>
|
||||
#include <esp_wifi.h>
|
||||
@@ -157,6 +158,12 @@ 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 {
|
||||
|
||||
@@ -65,7 +65,7 @@ void FanCall::validate_() {
|
||||
auto traits = this->parent_.get_traits();
|
||||
|
||||
if (this->speed_.has_value()) {
|
||||
this->speed_ = clamp(*this->speed_, 1, traits.supported_speed_count());
|
||||
this->speed_ = clamp(*this->speed_, 1, static_cast<int>(traits.supported_speed_count()));
|
||||
|
||||
// https://developers.home-assistant.io/docs/core/entity/fan/#preset-modes
|
||||
// "Manually setting a speed must disable any set preset mode"
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace fan {
|
||||
class FanTraits {
|
||||
public:
|
||||
FanTraits() = default;
|
||||
FanTraits(bool oscillation, bool speed, bool direction, int speed_count)
|
||||
FanTraits(bool oscillation, bool speed, bool direction, uint8_t speed_count)
|
||||
: oscillation_(oscillation), speed_(speed), direction_(direction), speed_count_(speed_count) {}
|
||||
|
||||
/// Return if this fan supports oscillation.
|
||||
@@ -23,9 +23,9 @@ class FanTraits {
|
||||
/// Set whether this fan supports speed levels.
|
||||
void set_speed(bool speed) { this->speed_ = speed; }
|
||||
/// Return how many speed levels the fan has
|
||||
int supported_speed_count() const { return this->speed_count_; }
|
||||
uint8_t supported_speed_count() const { return this->speed_count_; }
|
||||
/// Set how many speed levels this fan has.
|
||||
void set_supported_speed_count(int speed_count) { this->speed_count_ = speed_count; }
|
||||
void set_supported_speed_count(uint8_t speed_count) { this->speed_count_ = speed_count; }
|
||||
/// Return if this fan supports changing direction
|
||||
bool supports_direction() const { return this->direction_; }
|
||||
/// Set whether this fan supports changing direction
|
||||
@@ -61,7 +61,7 @@ class FanTraits {
|
||||
bool oscillation_{false};
|
||||
bool speed_{false};
|
||||
bool direction_{false};
|
||||
int speed_count_{};
|
||||
uint8_t speed_count_{};
|
||||
std::vector<const char *> preset_modes_{};
|
||||
};
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ CONFIG_SCHEMA = (
|
||||
cv.Optional(CONF_DECAY_MODE, default="SLOW"): cv.enum(
|
||||
DECAY_MODE_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1),
|
||||
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1, max=255),
|
||||
cv.Optional(CONF_ENABLE_PIN): cv.use_id(output.FloatOutput),
|
||||
cv.Optional(CONF_PRESET_MODES): validate_preset_modes,
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ enum DecayMode {
|
||||
|
||||
class HBridgeFan : public Component, public fan::Fan {
|
||||
public:
|
||||
HBridgeFan(int speed_count, DecayMode decay_mode) : speed_count_(speed_count), decay_mode_(decay_mode) {}
|
||||
HBridgeFan(uint8_t speed_count, DecayMode decay_mode) : speed_count_(speed_count), decay_mode_(decay_mode) {}
|
||||
|
||||
void set_pin_a(output::FloatOutput *pin_a) { pin_a_ = pin_a; }
|
||||
void set_pin_b(output::FloatOutput *pin_b) { pin_b_ = pin_b; }
|
||||
@@ -33,7 +33,7 @@ class HBridgeFan : public Component, public fan::Fan {
|
||||
output::FloatOutput *pin_b_;
|
||||
output::FloatOutput *enable_{nullptr};
|
||||
output::BinaryOutput *oscillating_{nullptr};
|
||||
int speed_count_{};
|
||||
uint8_t speed_count_{};
|
||||
DecayMode decay_mode_{DECAY_MODE_SLOW};
|
||||
fan::FanTraits traits_;
|
||||
std::vector<const char *> preset_modes_{};
|
||||
|
||||
@@ -7,30 +7,29 @@ namespace esphome::light {
|
||||
|
||||
// See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema
|
||||
|
||||
// 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;
|
||||
// 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];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
|
||||
|
||||
@@ -406,6 +406,8 @@ 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
|
||||
@@ -506,3 +508,24 @@ 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,8 +140,9 @@ 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
|
||||
|
||||
// Callbacks get message first (before console write)
|
||||
this->log_callback_.call(level, tag, this->tx_buffer_ + msg_start, msg_length);
|
||||
// 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);
|
||||
|
||||
// Write to console starting at the msg_start
|
||||
this->write_tx_buffer_to_console_(msg_start, &msg_length);
|
||||
@@ -203,7 +204,8 @@ 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_
|
||||
this->log_callback_.call(message->level, message->tag, this->tx_buffer_, msg_len);
|
||||
for (auto *listener : this->log_listeners_)
|
||||
listener->on_log(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);
|
||||
@@ -231,9 +233,6 @@ 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
|
||||
@@ -289,7 +288,10 @@ 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;
|
||||
this->level_callback_.call(level);
|
||||
#ifdef USE_LOGGER_LEVEL_LISTENERS
|
||||
for (auto *listener : this->level_listeners_)
|
||||
listener->on_log_level_change(level);
|
||||
#endif
|
||||
}
|
||||
|
||||
Logger *global_logger = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
@@ -36,6 +36,52 @@ 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 {
|
||||
@@ -168,11 +214,13 @@ class Logger : public Component {
|
||||
|
||||
inline uint8_t level_for(const char *tag);
|
||||
|
||||
/// 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);
|
||||
/// Register a log listener to receive log messages
|
||||
void add_log_listener(LogListener *listener) { this->log_listeners_.push_back(listener); }
|
||||
|
||||
// add a listener for log level changes
|
||||
void add_listener(std::function<void(uint8_t)> &&callback) { this->level_callback_.add(std::move(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
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
@@ -240,7 +288,7 @@ class Logger : public Component {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to format and send a log message to both console and callbacks
|
||||
// Helper to format and send a log message to both console and listeners
|
||||
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
|
||||
@@ -248,8 +296,9 @@ 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_);
|
||||
|
||||
// Callbacks get message WITHOUT newline (for API/MQTT/syslog)
|
||||
this->log_callback_.call(level, tag, this->tx_buffer_, this->tx_buffer_at_);
|
||||
// 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_);
|
||||
|
||||
// Console gets message WITH newline (if platform needs it)
|
||||
this->write_tx_buffer_to_console_();
|
||||
@@ -301,8 +350,10 @@ class Logger : public Component {
|
||||
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
|
||||
std::map<const char *, uint8_t, CStrCompare> log_levels_{};
|
||||
#endif
|
||||
CallbackManager<void(uint8_t, const char *, const char *, size_t)> log_callback_{};
|
||||
CallbackManager<void(uint8_t)> level_callback_{};
|
||||
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
|
||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||
std::unique_ptr<logger::TaskLogBuffer> log_buffer_; // Will be initialized with init_log_buffer
|
||||
#endif
|
||||
@@ -496,15 +547,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 *> {
|
||||
class LoggerMessageTrigger : public Trigger<uint8_t, const char *, const char *>, public LogListener {
|
||||
public:
|
||||
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);
|
||||
}
|
||||
});
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
@@ -5,7 +5,13 @@ 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
|
||||
from .. import (
|
||||
CONF_LOGGER_ID,
|
||||
LOG_LEVELS,
|
||||
Logger,
|
||||
logger_ns,
|
||||
request_logger_level_listeners,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
|
||||
@@ -21,6 +27,7 @@ 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::publish_state(int level) {
|
||||
void LoggerLevelSelect::on_log_level_change(uint8_t level) {
|
||||
auto index = level_to_index(level);
|
||||
if (!this->has_index(index))
|
||||
return;
|
||||
@@ -10,8 +10,8 @@ void LoggerLevelSelect::publish_state(int level) {
|
||||
}
|
||||
|
||||
void LoggerLevelSelect::setup() {
|
||||
this->parent_->add_listener([this](int level) { this->publish_state(level); });
|
||||
this->publish_state(this->parent_->get_log_level());
|
||||
this->parent_->add_level_listener(this);
|
||||
this->on_log_level_change(this->parent_->get_log_level());
|
||||
}
|
||||
|
||||
void LoggerLevelSelect::control(size_t index) { this->parent_->set_log_level(index_to_level(index)); }
|
||||
|
||||
@@ -5,12 +5,17 @@
|
||||
#include "esphome/components/logger/logger.h"
|
||||
|
||||
namespace esphome::logger {
|
||||
class LoggerLevelSelect : public Component, public select::Select, public Parented<Logger> {
|
||||
class LoggerLevelSelect final : public Component,
|
||||
public select::Select,
|
||||
public Parented<Logger>,
|
||||
public LoggerLevelListener {
|
||||
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,15 +57,7 @@ void MQTTClientComponent::setup() {
|
||||
});
|
||||
#ifdef USE_LOGGER
|
||||
if (this->is_log_message_enabled() && logger::global_logger != nullptr) {
|
||||
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});
|
||||
}
|
||||
});
|
||||
logger::global_logger->add_log_listener(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -148,6 +140,18 @@ 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,6 +10,9 @@
|
||||
#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)
|
||||
@@ -97,7 +100,12 @@ enum MQTTClientState {
|
||||
|
||||
class MQTTComponent;
|
||||
|
||||
class MQTTClientComponent : public Component {
|
||||
class MQTTClientComponent : public Component
|
||||
#ifdef USE_LOGGER
|
||||
,
|
||||
public logger::LogListener
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
MQTTClientComponent();
|
||||
|
||||
@@ -238,6 +246,10 @@ 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,7 +2,8 @@ import logging
|
||||
from pathlib import Path
|
||||
|
||||
from esphome import git, yaml_util
|
||||
from esphome.config_helpers import merge_config
|
||||
from esphome.components.substitutions.jinja import has_jinja
|
||||
from esphome.config_helpers import Remove, merge_config
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ESPHOME,
|
||||
@@ -39,10 +40,15 @@ 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)):
|
||||
continue # e.g. script: [] or logger: {level: debug}
|
||||
if isinstance(v, (dict, list, Remove)):
|
||||
continue # e.g. script: [], psram: !remove, 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
|
||||
|
||||
|
||||
@@ -270,7 +270,9 @@ ThrottleFilter = sensor_ns.class_("ThrottleFilter", Filter)
|
||||
ThrottleWithPriorityFilter = sensor_ns.class_(
|
||||
"ThrottleWithPriorityFilter", ValueListFilter
|
||||
)
|
||||
TimeoutFilter = sensor_ns.class_("TimeoutFilter", Filter, cg.Component)
|
||||
TimeoutFilterBase = sensor_ns.class_("TimeoutFilterBase", Filter, cg.Component)
|
||||
TimeoutFilterLast = sensor_ns.class_("TimeoutFilterLast", TimeoutFilterBase)
|
||||
TimeoutFilterConfigured = sensor_ns.class_("TimeoutFilterConfigured", TimeoutFilterBase)
|
||||
DebounceFilter = sensor_ns.class_("DebounceFilter", Filter, cg.Component)
|
||||
HeartbeatFilter = sensor_ns.class_("HeartbeatFilter", Filter, cg.Component)
|
||||
DeltaFilter = sensor_ns.class_("DeltaFilter", Filter)
|
||||
@@ -681,11 +683,16 @@ TIMEOUT_SCHEMA = cv.maybe_simple_value(
|
||||
)
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register("timeout", TimeoutFilter, TIMEOUT_SCHEMA)
|
||||
@FILTER_REGISTRY.register("timeout", TimeoutFilterBase, 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,20 +339,43 @@ void OrFilter::initialize(Sensor *parent, Filter *next) {
|
||||
this->phi_.initialize(parent, nullptr);
|
||||
}
|
||||
|
||||
// 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); });
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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; }
|
||||
// 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;
|
||||
}
|
||||
|
||||
// DebounceFilter
|
||||
optional<float> DebounceFilter::new_value(float value) {
|
||||
|
||||
@@ -380,18 +380,46 @@ class ThrottleWithPriorityFilter : public ValueListFilter {
|
||||
uint32_t min_time_between_inputs_;
|
||||
};
|
||||
|
||||
class TimeoutFilter : public Filter, public Component {
|
||||
// Base class for timeout filters - contains common loop logic
|
||||
class TimeoutFilterBase : public Filter, public Component {
|
||||
public:
|
||||
explicit TimeoutFilter(uint32_t time_period);
|
||||
explicit TimeoutFilter(uint32_t time_period, const TemplatableValue<float> &new_value);
|
||||
|
||||
optional<float> new_value(float value) override;
|
||||
|
||||
void loop() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
uint32_t time_period_;
|
||||
optional<TemplatableValue<float>> value_;
|
||||
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
|
||||
};
|
||||
|
||||
class DebounceFilter : public Filter, public Component {
|
||||
|
||||
@@ -25,7 +25,7 @@ CONFIG_SCHEMA = (
|
||||
cv.Optional(CONF_SPEED): cv.invalid(
|
||||
"Configuring individual speeds is deprecated."
|
||||
),
|
||||
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1),
|
||||
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1, max=255),
|
||||
cv.Optional(CONF_PRESET_MODES): validate_preset_modes,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace speed {
|
||||
|
||||
class SpeedFan : public Component, public fan::Fan {
|
||||
public:
|
||||
SpeedFan(int speed_count) : speed_count_(speed_count) {}
|
||||
SpeedFan(uint8_t speed_count) : speed_count_(speed_count) {}
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void set_output(output::FloatOutput *output) { this->output_ = output; }
|
||||
@@ -26,7 +26,7 @@ class SpeedFan : public Component, public fan::Fan {
|
||||
output::FloatOutput *output_;
|
||||
output::BinaryOutput *oscillating_{nullptr};
|
||||
output::BinaryOutput *direction_{nullptr};
|
||||
int speed_count_{};
|
||||
uint8_t speed_count_{};
|
||||
fan::FanTraits traits_;
|
||||
std::vector<const char *> preset_modes_{};
|
||||
};
|
||||
|
||||
@@ -19,11 +19,10 @@ constexpr int LOG_LEVEL_TO_SYSLOG_SEVERITY[] = {
|
||||
7 // VERY_VERBOSE
|
||||
};
|
||||
|
||||
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::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::log_(const int level, const char *tag, const char *message, size_t message_len) const {
|
||||
|
||||
@@ -2,16 +2,18 @@
|
||||
#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> {
|
||||
class Syslog : public Component, public Parented<udp::UDPComponent>, public logger::LogListener {
|
||||
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; }
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ CONFIG_SCHEMA = (
|
||||
{
|
||||
cv.Optional(CONF_HAS_DIRECTION, default=False): cv.boolean,
|
||||
cv.Optional(CONF_HAS_OSCILLATING, default=False): cv.boolean,
|
||||
cv.Optional(CONF_SPEED_COUNT): cv.int_range(min=1),
|
||||
cv.Optional(CONF_SPEED_COUNT): cv.int_range(min=1, max=255),
|
||||
cv.Optional(CONF_PRESET_MODES): validate_preset_modes,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ class TemplateFan final : public Component, public fan::Fan {
|
||||
void dump_config() override;
|
||||
void set_has_direction(bool has_direction) { this->has_direction_ = has_direction; }
|
||||
void set_has_oscillating(bool has_oscillating) { this->has_oscillating_ = has_oscillating; }
|
||||
void set_speed_count(int count) { this->speed_count_ = count; }
|
||||
void set_speed_count(uint8_t count) { this->speed_count_ = count; }
|
||||
void set_preset_modes(std::initializer_list<const char *> presets) { this->preset_modes_ = presets; }
|
||||
fan::FanTraits get_traits() override { return this->traits_; }
|
||||
|
||||
@@ -22,7 +22,7 @@ class TemplateFan final : public Component, public fan::Fan {
|
||||
|
||||
bool has_oscillating_{false};
|
||||
bool has_direction_{false};
|
||||
int speed_count_{0};
|
||||
uint8_t speed_count_{0};
|
||||
fan::FanTraits traits_;
|
||||
std::vector<const char *> preset_modes_{};
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_SPEED_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_DIRECTION_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_SPEED_COUNT, default=3): cv.int_range(min=1, max=256),
|
||||
cv.Optional(CONF_SPEED_COUNT, default=3): cv.int_range(min=1, max=255),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace tuya {
|
||||
|
||||
class TuyaFan : public Component, public fan::Fan {
|
||||
public:
|
||||
TuyaFan(Tuya *parent, int speed_count) : parent_(parent), speed_count_(speed_count) {}
|
||||
TuyaFan(Tuya *parent, uint8_t speed_count) : parent_(parent), speed_count_(speed_count) {}
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void set_speed_id(uint8_t speed_id) { this->speed_id_ = speed_id; }
|
||||
@@ -27,7 +27,7 @@ class TuyaFan : public Component, public fan::Fan {
|
||||
optional<uint8_t> switch_id_{};
|
||||
optional<uint8_t> oscillation_id_{};
|
||||
optional<uint8_t> direction_id_{};
|
||||
int speed_count_{};
|
||||
uint8_t speed_count_{};
|
||||
TuyaDatapointType speed_type_{};
|
||||
TuyaDatapointType oscillation_type_{};
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import socket
|
||||
from esphome.components.uart import (
|
||||
CONF_DATA_BITS,
|
||||
CONF_PARITY,
|
||||
@@ -17,7 +18,7 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.cpp_types import Component
|
||||
|
||||
AUTO_LOAD = ["uart", "usb_host", "bytebuffer"]
|
||||
AUTO_LOAD = ["uart", "usb_host", "bytebuffer", "socket"]
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
|
||||
usb_uart_ns = cg.esphome_ns.namespace("usb_uart")
|
||||
@@ -116,6 +117,10 @@ 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,6 +2,7 @@
|
||||
#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>
|
||||
@@ -262,6 +263,11 @@ 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,12 +301,7 @@ void WebServer::setup() {
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
if (logger::global_logger != nullptr && this->expose_log_) {
|
||||
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());
|
||||
});
|
||||
logger::global_logger->add_log_listener(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -322,6 +317,16 @@ 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,6 +7,9 @@
|
||||
#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>
|
||||
@@ -170,7 +173,14 @@ 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 {
|
||||
class WebServer : public Controller,
|
||||
public Component,
|
||||
public AsyncWebHandler
|
||||
#ifdef USE_LOGGER
|
||||
,
|
||||
public logger::LogListener
|
||||
#endif
|
||||
{
|
||||
#if !defined(USE_ESP32) && defined(USE_ARDUINO)
|
||||
friend class DeferredUpdateEventSourceList;
|
||||
#endif
|
||||
@@ -230,6 +240,10 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
|
||||
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_CALLBACKS_KEY = "wifi_callbacks"
|
||||
WIFI_LISTENERS_KEY = "wifi_listeners"
|
||||
|
||||
|
||||
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_callbacks() -> None:
|
||||
"""Request that WiFi callbacks be compiled in.
|
||||
def request_wifi_listeners() -> None:
|
||||
"""Request that WiFi state listeners 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_on_ip_state_callback(), add_on_wifi_scan_state_callback(),
|
||||
and add_on_wifi_connect_state_callback() APIs.
|
||||
This enables the add_ip_state_listener(), add_scan_results_listener(),
|
||||
and add_connect_state_listener() APIs.
|
||||
"""
|
||||
CORE.data[WIFI_CALLBACKS_KEY] = True
|
||||
CORE.data[WIFI_LISTENERS_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_CALLBACKS_KEY, False):
|
||||
cg.add_define("USE_WIFI_CALLBACKS")
|
||||
if CORE.data.get(WIFI_LISTENERS_KEY, False):
|
||||
cg.add_define("USE_WIFI_LISTENERS")
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
|
||||
@@ -1612,6 +1612,20 @@ 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,6 +242,37 @@ 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:
|
||||
@@ -284,6 +315,10 @@ 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();
|
||||
@@ -369,26 +404,22 @@ class WiFiComponent : public Component {
|
||||
|
||||
int32_t get_wifi_channel();
|
||||
|
||||
#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));
|
||||
#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);
|
||||
}
|
||||
/// - 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));
|
||||
/** 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 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
|
||||
#endif // USE_WIFI_LISTENERS
|
||||
|
||||
#ifdef USE_WIFI_RUNTIME_POWER_SAVE
|
||||
/** Request high-performance mode (no power saving) for improved WiFi latency.
|
||||
@@ -546,11 +577,11 @@ class WiFiComponent : public Component {
|
||||
WiFiAP ap_;
|
||||
#endif
|
||||
optional<float> output_power_;
|
||||
#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
|
||||
#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
|
||||
ESPPreferenceObject pref_;
|
||||
#ifdef USE_WIFI_FAST_CONNECT
|
||||
ESPPreferenceObject fast_connect_pref_;
|
||||
|
||||
@@ -513,9 +513,10 @@ 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_CALLBACKS
|
||||
global_wifi_component->wifi_connect_state_callback_.call(global_wifi_component->wifi_ssid(),
|
||||
global_wifi_component->wifi_bssid());
|
||||
#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());
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -536,8 +537,10 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
}
|
||||
s_sta_connected = false;
|
||||
s_sta_connecting = false;
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
global_wifi_component->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0}));
|
||||
#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}));
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -561,10 +564,11 @@ 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_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));
|
||||
#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));
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -740,8 +744,10 @@ void WiFiComponent::wifi_scan_done_callback_(void *arg, STATUS status) {
|
||||
it->is_hidden != 0);
|
||||
}
|
||||
this->scan_done_ = true;
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
global_wifi_component->wifi_scan_state_callback_.call(global_wifi_component->scan_result_);
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : global_wifi_component->scan_results_listeners_) {
|
||||
listener->on_wifi_scan_results(global_wifi_component->scan_result_);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -878,10 +884,9 @@ network::IPAddress WiFiComponent::wifi_soft_ap_ip() {
|
||||
|
||||
bssid_t WiFiComponent::wifi_bssid() {
|
||||
bssid_t bssid{};
|
||||
uint8_t *raw_bssid = WiFi.BSSID();
|
||||
if (raw_bssid != nullptr) {
|
||||
for (size_t i = 0; i < bssid.size(); i++)
|
||||
bssid[i] = raw_bssid[i];
|
||||
struct station_config conf {};
|
||||
if (wifi_station_get_config(&conf)) {
|
||||
std::copy_n(conf.bssid, bssid.size(), bssid.begin());
|
||||
}
|
||||
return bssid;
|
||||
}
|
||||
|
||||
@@ -727,8 +727,10 @@ 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_CALLBACKS
|
||||
this->wifi_connect_state_callback_.call(this->wifi_ssid(), this->wifi_bssid());
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid());
|
||||
}
|
||||
#endif
|
||||
|
||||
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
@@ -753,8 +755,10 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
s_sta_connected = false;
|
||||
s_sta_connecting = false;
|
||||
error_from_callback_ = true;
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0}));
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0}));
|
||||
}
|
||||
#endif
|
||||
|
||||
} else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_GOT_IP) {
|
||||
@@ -764,8 +768,10 @@ 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_CALLBACKS
|
||||
this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
#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));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if USE_NETWORK_IPV6
|
||||
@@ -773,8 +779,10 @@ 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_CALLBACKS
|
||||
this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
#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));
|
||||
}
|
||||
#endif
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
@@ -815,8 +823,10 @@ 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_CALLBACKS
|
||||
this->wifi_scan_state_callback_.call(this->scan_result_);
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->scan_results_listeners_) {
|
||||
listener->on_wifi_scan_results(this->scan_result_);
|
||||
}
|
||||
#endif
|
||||
|
||||
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_START) {
|
||||
|
||||
@@ -287,8 +287,10 @@ 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_CALLBACKS
|
||||
this->wifi_connect_state_callback_.call(this->wifi_ssid(), this->wifi_bssid());
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid());
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -315,8 +317,10 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
||||
}
|
||||
|
||||
s_sta_connecting = false;
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0}));
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0}));
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -339,16 +343,20 @@ 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_CALLBACKS
|
||||
this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
#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));
|
||||
}
|
||||
#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_CALLBACKS
|
||||
this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
#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));
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -443,8 +451,10 @@ void WiFiComponent::wifi_scan_done_callback_() {
|
||||
}
|
||||
WiFi.scanDelete();
|
||||
this->scan_done_ = true;
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->wifi_scan_state_callback_.call(this->scan_result_);
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->scan_results_listeners_) {
|
||||
listener->on_wifi_scan_results(this->scan_result_);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -225,8 +225,10 @@ 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_CALLBACKS
|
||||
this->wifi_scan_state_callback_.call(this->scan_result_);
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->scan_results_listeners_) {
|
||||
listener->on_wifi_scan_results(this->scan_result_);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -241,16 +243,20 @@ void WiFiComponent::wifi_loop_() {
|
||||
// Just connected
|
||||
s_sta_was_connected = true;
|
||||
ESP_LOGV(TAG, "Connected");
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->wifi_connect_state_callback_.call(this->wifi_ssid(), this->wifi_bssid());
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(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_CALLBACKS
|
||||
this->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0}));
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0}));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -267,8 +273,10 @@ void WiFiComponent::wifi_loop_() {
|
||||
// Just got IP address
|
||||
s_sta_had_ip = true;
|
||||
ESP_LOGV(TAG, "Got IP address");
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
#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));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
# Keys that require WiFi callbacks
|
||||
# Keys that require WiFi listeners
|
||||
_NETWORK_INFO_KEYS = {
|
||||
CONF_SSID,
|
||||
CONF_BSSID,
|
||||
@@ -79,9 +79,9 @@ async def setup_conf(config, key):
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
# Request WiFi callbacks for any sensor that needs them
|
||||
# Request WiFi listeners for any sensor that needs them
|
||||
if _NETWORK_INFO_KEYS.intersection(config):
|
||||
wifi.request_wifi_callbacks()
|
||||
wifi.request_wifi_listeners()
|
||||
|
||||
await setup_conf(config, CONF_SSID)
|
||||
await setup_conf(config, CONF_BSSID)
|
||||
|
||||
@@ -12,16 +12,12 @@ static constexpr size_t MAX_STATE_LENGTH = 255;
|
||||
* IPAddressWiFiInfo
|
||||
*******************/
|
||||
|
||||
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::setup() { wifi::global_wifi_component->add_ip_state_listener(this); }
|
||||
|
||||
void IPAddressWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "IP Address", this); }
|
||||
|
||||
void IPAddressWiFiInfo::state_callback_(const network::IPAddresses &ips) {
|
||||
void IPAddressWiFiInfo::on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1,
|
||||
const network::IPAddress &dns2) {
|
||||
this->publish_state(ips[0].str());
|
||||
uint8_t sensor = 0;
|
||||
for (const auto &ip : ips) {
|
||||
@@ -38,17 +34,13 @@ void IPAddressWiFiInfo::state_callback_(const network::IPAddresses &ips) {
|
||||
* DNSAddressWifiInfo
|
||||
********************/
|
||||
|
||||
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::setup() { wifi::global_wifi_component->add_ip_state_listener(this); }
|
||||
|
||||
void DNSAddressWifiInfo::dump_config() { LOG_TEXT_SENSOR("", "DNS Address", this); }
|
||||
|
||||
void DNSAddressWifiInfo::state_callback_(const network::IPAddress &dns1_ip, const network::IPAddress &dns2_ip) {
|
||||
std::string dns_results = dns1_ip.str() + " " + dns2_ip.str();
|
||||
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();
|
||||
this->publish_state(dns_results);
|
||||
}
|
||||
|
||||
@@ -56,14 +48,11 @@ void DNSAddressWifiInfo::state_callback_(const network::IPAddress &dns1_ip, cons
|
||||
* ScanResultsWiFiInfo
|
||||
*********************/
|
||||
|
||||
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::setup() { wifi::global_wifi_component->add_scan_results_listener(this); }
|
||||
|
||||
void ScanResultsWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "Scan Results", this); }
|
||||
|
||||
void ScanResultsWiFiInfo::state_callback_(const wifi::wifi_scan_vector_t<wifi::WiFiScanResult> &results) {
|
||||
void ScanResultsWiFiInfo::on_wifi_scan_results(const wifi::wifi_scan_vector_t<wifi::WiFiScanResult> &results) {
|
||||
std::string scan_results;
|
||||
for (const auto &scan : results) {
|
||||
if (scan.get_is_hidden())
|
||||
@@ -85,33 +74,30 @@ void ScanResultsWiFiInfo::state_callback_(const wifi::wifi_scan_vector_t<wifi::W
|
||||
* SSIDWiFiInfo
|
||||
**************/
|
||||
|
||||
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::setup() { wifi::global_wifi_component->add_connect_state_listener(this); }
|
||||
|
||||
void SSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "SSID", this); }
|
||||
|
||||
void SSIDWiFiInfo::state_callback_(const std::string &ssid) { this->publish_state(ssid); }
|
||||
void SSIDWiFiInfo::on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) {
|
||||
this->publish_state(ssid);
|
||||
}
|
||||
|
||||
/****************
|
||||
* BSSIDWiFiInfo
|
||||
***************/
|
||||
|
||||
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::setup() { wifi::global_wifi_component->add_connect_state_listener(this); }
|
||||
|
||||
void BSSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "BSSID", this); }
|
||||
|
||||
void BSSIDWiFiInfo::state_callback_(const wifi::bssid_t &bssid) {
|
||||
void BSSIDWiFiInfo::on_wifi_connect_state(const std::string &ssid, 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,55 +9,61 @@
|
||||
|
||||
namespace esphome::wifi_info {
|
||||
|
||||
class IPAddressWiFiInfo : public Component, public text_sensor::TextSensor {
|
||||
class IPAddressWiFiInfo final : public Component, public text_sensor::TextSensor, public wifi::WiFiIPStateListener {
|
||||
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 : public Component, public text_sensor::TextSensor {
|
||||
class DNSAddressWifiInfo final : public Component, public text_sensor::TextSensor, public wifi::WiFiIPStateListener {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
void state_callback_(const network::IPAddress &dns1_ip, const network::IPAddress &dns2_ip);
|
||||
// WiFiIPStateListener interface
|
||||
void on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1,
|
||||
const network::IPAddress &dns2) override;
|
||||
};
|
||||
|
||||
class ScanResultsWiFiInfo : public Component, public text_sensor::TextSensor {
|
||||
class ScanResultsWiFiInfo final : public Component,
|
||||
public text_sensor::TextSensor,
|
||||
public wifi::WiFiScanResultsListener {
|
||||
public:
|
||||
void setup() override;
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
void state_callback_(const wifi::wifi_scan_vector_t<wifi::WiFiScanResult> &results);
|
||||
// WiFiScanResultsListener interface
|
||||
void on_wifi_scan_results(const wifi::wifi_scan_vector_t<wifi::WiFiScanResult> &results) override;
|
||||
};
|
||||
|
||||
class SSIDWiFiInfo : public Component, public text_sensor::TextSensor {
|
||||
class SSIDWiFiInfo final : public Component, public text_sensor::TextSensor, public wifi::WiFiConnectStateListener {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
void state_callback_(const std::string &ssid);
|
||||
// WiFiConnectStateListener interface
|
||||
void on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) override;
|
||||
};
|
||||
|
||||
class BSSIDWiFiInfo : public Component, public text_sensor::TextSensor {
|
||||
class BSSIDWiFiInfo final : public Component, public text_sensor::TextSensor, public wifi::WiFiConnectStateListener {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
void state_callback_(const wifi::bssid_t &bssid);
|
||||
// WiFiConnectStateListener interface
|
||||
void on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) override;
|
||||
};
|
||||
|
||||
class MacAddressWifiInfo : public Component, public text_sensor::TextSensor {
|
||||
class MacAddressWifiInfo final : public Component, public text_sensor::TextSensor {
|
||||
public:
|
||||
void setup() override {
|
||||
char mac_s[18];
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
#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
|
||||
@@ -210,7 +211,7 @@
|
||||
#define USE_WEBSERVER_SORTING
|
||||
#define USE_WIFI_11KV_SUPPORT
|
||||
#define USE_WIFI_FAST_CONNECT
|
||||
#define USE_WIFI_CALLBACKS
|
||||
#define USE_WIFI_LISTENERS
|
||||
#define USE_WIFI_RUNTIME_POWER_SAVE
|
||||
#define USB_HOST_MAX_REQUESTS 16
|
||||
|
||||
|
||||
@@ -359,8 +359,7 @@ void HOT Scheduler::call(uint32_t now) {
|
||||
std::unique_ptr<SchedulerItem> item;
|
||||
{
|
||||
LockGuard guard{this->lock_};
|
||||
item = std::move(this->items_[0]);
|
||||
this->pop_raw_();
|
||||
item = this->pop_raw_locked_();
|
||||
}
|
||||
|
||||
const char *name = item->get_name();
|
||||
@@ -401,7 +400,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->pop_raw_();
|
||||
this->recycle_item_(this->pop_raw_locked_());
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -414,7 +413,7 @@ void HOT Scheduler::call(uint32_t now) {
|
||||
{
|
||||
LockGuard guard{this->lock_};
|
||||
if (is_item_removed_(item.get())) {
|
||||
this->pop_raw_();
|
||||
this->recycle_item_(this->pop_raw_locked_());
|
||||
this->to_remove_--;
|
||||
continue;
|
||||
}
|
||||
@@ -423,7 +422,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->pop_raw_();
|
||||
this->recycle_item_(this->pop_raw_locked_());
|
||||
this->to_remove_--;
|
||||
continue;
|
||||
}
|
||||
@@ -443,14 +442,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.
|
||||
this->pop_raw_();
|
||||
auto executed_item = this->pop_raw_locked_();
|
||||
|
||||
if (executed_item->remove) {
|
||||
// We were removed/cancelled in the function call, stop
|
||||
// We were removed/cancelled in the function call, recycle and continue
|
||||
this->to_remove_--;
|
||||
this->recycle_item_(std::move(executed_item));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -497,7 +496,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_) which requires exclusive access
|
||||
// 1. We're modifying items_ (via pop_raw_locked_) 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_()
|
||||
@@ -510,17 +509,18 @@ size_t HOT Scheduler::cleanup_() {
|
||||
if (!item->remove)
|
||||
break;
|
||||
this->to_remove_--;
|
||||
this->pop_raw_();
|
||||
this->recycle_item_(this->pop_raw_locked_());
|
||||
}
|
||||
return this->items_.size();
|
||||
}
|
||||
void HOT Scheduler::pop_raw_() {
|
||||
std::unique_ptr<Scheduler::SchedulerItem> HOT Scheduler::pop_raw_locked_() {
|
||||
std::pop_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
|
||||
|
||||
// Instead of destroying, recycle the item
|
||||
this->recycle_item_(std::move(this->items_.back()));
|
||||
// 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());
|
||||
|
||||
this->items_.pop_back();
|
||||
return item;
|
||||
}
|
||||
|
||||
// Helper to execute a scheduler item
|
||||
|
||||
@@ -219,7 +219,9 @@ 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_();
|
||||
void pop_raw_();
|
||||
// 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_();
|
||||
|
||||
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, 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"
|
||||
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"
|
||||
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, uint8_t *msg_data) override;\n"
|
||||
hpp_protected += " void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;\n"
|
||||
|
||||
cpp += f"\nvoid {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {{\n"
|
||||
cpp += f"\nvoid {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {{\n"
|
||||
cpp += " // Check authentication/connection requirements for messages\n"
|
||||
cpp += " switch (msg_type) {\n"
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
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
|
||||
@@ -0,0 +1,43 @@
|
||||
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
|
||||
@@ -0,0 +1,6 @@
|
||||
# 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}
|
||||
@@ -0,0 +1,4 @@
|
||||
# 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}
|
||||
@@ -0,0 +1,3 @@
|
||||
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`.
|
||||
@@ -0,0 +1,9 @@
|
||||
defaults:
|
||||
z: 5
|
||||
package_name: default
|
||||
values_from_repo1_main:
|
||||
- package_name: ${package_name}
|
||||
x: ${x}
|
||||
y: ${y}
|
||||
z: ${z}
|
||||
volume: ${x*y*z}
|
||||
@@ -0,0 +1,10 @@
|
||||
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,6 +2,7 @@ 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
|
||||
@@ -84,11 +85,41 @@ def verify_database(value: Any, path: str = "") -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def test_substitutions_fixtures(fixture_path):
|
||||
# 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):
|
||||
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