1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-31 23:21:54 +00:00

Merge branch 'dev' into jesserockz-2025-457

This commit is contained in:
Jesse Hills
2025-10-07 07:35:49 +13:00
180 changed files with 2516 additions and 1640 deletions

View File

@@ -1 +1 @@
4368db58e8f884aff245996b1e8b644cc0796c0bb2fa706d5740d40b823d3ac9 499db61c1aa55b98b6629df603a56a1ba7aff5a9a7c781a5c1552a9dcd186c08

View File

@@ -466,7 +466,7 @@ jobs:
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }} cache-key: ${{ needs.common.outputs.cache-key }}
- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 - uses: esphome/action@43cd1109c09c544d97196f7730ee5b2e0cc6d81e # v3.0.1 fork with pinned actions/cache
env: env:
SKIP: pylint,clang-tidy-hash SKIP: pylint,clang-tidy-hash
- uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0 - uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0

View File

@@ -58,7 +58,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 uses: github/codeql-action/init@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }} build-mode: ${{ matrix.build-mode }}
@@ -86,6 +86,6 @@ jobs:
exit 1 exit 1
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 uses: github/codeql-action/analyze@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6
with: with:
category: "/language:${{matrix.language}}" category: "/language:${{matrix.language}}"

View File

@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Stale - name: Stale
uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
with: with:
debug-only: ${{ github.ref != 'refs/heads/dev' }} # Dry-run when not run on dev branch debug-only: ${{ github.ref != 'refs/heads/dev' }} # Dry-run when not run on dev branch
remove-stale-when-updated: true remove-stale-when-updated: true

View File

@@ -11,7 +11,7 @@ ci:
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version. # Ruff version.
rev: v0.13.2 rev: v0.13.3
hooks: hooks:
# Run the linter. # Run the linter.
- id: ruff - id: ruff

View File

@@ -160,7 +160,6 @@ esphome/components/esp_ldo/* @clydebarrow
esphome/components/espnow/* @jesserockz esphome/components/espnow/* @jesserockz
esphome/components/ethernet_info/* @gtjadsonsantos esphome/components/ethernet_info/* @gtjadsonsantos
esphome/components/event/* @nohat esphome/components/event/* @nohat
esphome/components/event_emitter/* @Rapsssito
esphome/components/exposure_notifications/* @OttoWinter esphome/components/exposure_notifications/* @OttoWinter
esphome/components/ezo/* @ssieb esphome/components/ezo/* @ssieb
esphome/components/ezo_pmp/* @carlos-sarmiento esphome/components/ezo_pmp/* @carlos-sarmiento
@@ -257,6 +256,7 @@ esphome/components/libretiny_pwm/* @kuba2k2
esphome/components/light/* @esphome/core esphome/components/light/* @esphome/core
esphome/components/lightwaverf/* @max246 esphome/components/lightwaverf/* @max246
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
esphome/components/lm75b/* @beormund
esphome/components/ln882x/* @lamauny esphome/components/ln882x/* @lamauny
esphome/components/lock/* @esphome/core esphome/components/lock/* @esphome/core
esphome/components/logger/* @esphome/core esphome/components/logger/* @esphome/core

View File

@@ -14,9 +14,11 @@ from typing import Protocol
import argcomplete import argcomplete
# Note: Do not import modules from esphome.components here, as this would
# cause them to be loaded before external components are processed, resulting
# in the built-in version being used instead of the external component one.
from esphome import const, writer, yaml_util from esphome import const, writer, yaml_util
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components.mqtt import CONF_DISCOVER_IP
from esphome.config import iter_component_configs, read_config, strip_default_ids from esphome.config import iter_component_configs, read_config, strip_default_ids
from esphome.const import ( from esphome.const import (
ALLOWED_NAME_CHARS, ALLOWED_NAME_CHARS,
@@ -240,6 +242,8 @@ def has_ota() -> bool:
def has_mqtt_ip_lookup() -> bool: def has_mqtt_ip_lookup() -> bool:
"""Check if MQTT is available and IP lookup is supported.""" """Check if MQTT is available and IP lookup is supported."""
from esphome.components.mqtt import CONF_DISCOVER_IP
if CONF_MQTT not in CORE.config: if CONF_MQTT not in CORE.config:
return False return False
# Default Enabled # Default Enabled

View File

@@ -26,12 +26,12 @@ uint32_t Animation::get_animation_frame_count() const { return this->animation_f
int Animation::get_current_frame() const { return this->current_frame_; } int Animation::get_current_frame() const { return this->current_frame_; }
void Animation::next_frame() { void Animation::next_frame() {
this->current_frame_++; this->current_frame_++;
if (loop_count_ && this->current_frame_ == loop_end_frame_ && if (loop_count_ && static_cast<uint32_t>(this->current_frame_) == loop_end_frame_ &&
(this->loop_current_iteration_ < loop_count_ || loop_count_ < 0)) { (this->loop_current_iteration_ < loop_count_ || loop_count_ < 0)) {
this->current_frame_ = loop_start_frame_; this->current_frame_ = loop_start_frame_;
this->loop_current_iteration_++; this->loop_current_iteration_++;
} }
if (this->current_frame_ >= animation_frame_count_) { if (static_cast<uint32_t>(this->current_frame_) >= animation_frame_count_) {
this->loop_current_iteration_ = 1; this->loop_current_iteration_ = 1;
this->current_frame_ = 0; this->current_frame_ = 0;
} }

View File

@@ -14,6 +14,7 @@ from esphome.const import (
CONF_EVENT, CONF_EVENT,
CONF_ID, CONF_ID,
CONF_KEY, CONF_KEY,
CONF_MAX_CONNECTIONS,
CONF_ON_CLIENT_CONNECTED, CONF_ON_CLIENT_CONNECTED,
CONF_ON_CLIENT_DISCONNECTED, CONF_ON_CLIENT_DISCONNECTED,
CONF_ON_ERROR, CONF_ON_ERROR,
@@ -68,7 +69,7 @@ CONF_CUSTOM_SERVICES = "custom_services"
CONF_HOMEASSISTANT_SERVICES = "homeassistant_services" CONF_HOMEASSISTANT_SERVICES = "homeassistant_services"
CONF_HOMEASSISTANT_STATES = "homeassistant_states" CONF_HOMEASSISTANT_STATES = "homeassistant_states"
CONF_LISTEN_BACKLOG = "listen_backlog" CONF_LISTEN_BACKLOG = "listen_backlog"
CONF_MAX_CONNECTIONS = "max_connections" CONF_MAX_SEND_QUEUE = "max_send_queue"
def validate_encryption_key(value): def validate_encryption_key(value):
@@ -191,6 +192,19 @@ CONFIG_SCHEMA = cv.All(
host=8, # Abundant resources host=8, # Abundant resources
ln882x=8, # Moderate RAM ln882x=8, # Moderate RAM
): cv.int_range(min=1, max=20), ): cv.int_range(min=1, max=20),
# Maximum queued send buffers per connection before dropping connection
# Each buffer uses ~8-12 bytes overhead plus actual message size
# Platform defaults based on available RAM and typical message rates:
cv.SplitDefault(
CONF_MAX_SEND_QUEUE,
esp8266=5, # Limited RAM, need to fail fast
esp32=8, # More RAM, can buffer more
rp2040=5, # Limited RAM
bk72xx=8, # Moderate RAM
rtl87xx=8, # Moderate RAM
host=16, # Abundant resources
ln882x=8, # Moderate RAM
): cv.int_range(min=1, max=64),
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
cv.rename_key(CONF_SERVICES, CONF_ACTIONS), cv.rename_key(CONF_SERVICES, CONF_ACTIONS),
@@ -213,6 +227,7 @@ async def to_code(config):
cg.add(var.set_listen_backlog(config[CONF_LISTEN_BACKLOG])) cg.add(var.set_listen_backlog(config[CONF_LISTEN_BACKLOG]))
if CONF_MAX_CONNECTIONS in config: if CONF_MAX_CONNECTIONS in config:
cg.add(var.set_max_connections(config[CONF_MAX_CONNECTIONS])) cg.add(var.set_max_connections(config[CONF_MAX_CONNECTIONS]))
cg.add_define("API_MAX_SEND_QUEUE", config[CONF_MAX_SEND_QUEUE])
# Set USE_API_SERVICES if any services are enabled # Set USE_API_SERVICES if any services are enabled
if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]: if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:

View File

@@ -116,8 +116,7 @@ void APIConnection::start() {
APIError err = this->helper_->init(); APIError err = this->helper_->init();
if (err != APIError::OK) { if (err != APIError::OK) {
on_fatal_error(); this->fatal_error_with_log_(LOG_STR("Helper init failed"), err);
this->log_warning_(LOG_STR("Helper init failed"), err);
return; return;
} }
this->client_info_.peername = helper_->getpeername(); this->client_info_.peername = helper_->getpeername();
@@ -147,8 +146,7 @@ void APIConnection::loop() {
APIError err = this->helper_->loop(); APIError err = this->helper_->loop();
if (err != APIError::OK) { if (err != APIError::OK) {
on_fatal_error(); this->fatal_error_with_log_(LOG_STR("Socket operation failed"), err);
this->log_socket_operation_failed_(err);
return; return;
} }
@@ -163,17 +161,13 @@ void APIConnection::loop() {
// No more data available // No more data available
break; break;
} else if (err != APIError::OK) { } else if (err != APIError::OK) {
on_fatal_error(); this->fatal_error_with_log_(LOG_STR("Reading failed"), err);
this->log_warning_(LOG_STR("Reading failed"), err);
return; return;
} else { } else {
this->last_traffic_ = now; this->last_traffic_ = now;
// read a packet // read a packet
if (buffer.data_len > 0) { this->read_message(buffer.data_len, buffer.type,
this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]); buffer.data_len > 0 ? &buffer.container[buffer.data_offset] : nullptr);
} else {
this->read_message(0, buffer.type, nullptr);
}
if (this->flags_.remove) if (this->flags_.remove)
return; return;
} }
@@ -205,7 +199,8 @@ void APIConnection::loop() {
// Disconnect if not responded within 2.5*keepalive // Disconnect if not responded within 2.5*keepalive
if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) { if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) {
on_fatal_error(); on_fatal_error();
ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->get_client_combined_info().c_str()); ESP_LOGW(TAG, "%s (%s) is unresponsive; disconnecting", this->client_info_.name.c_str(),
this->client_info_.peername.c_str());
} }
} else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && !this->flags_.remove) { } else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && !this->flags_.remove) {
// Only send ping if we're not disconnecting // Only send ping if we're not disconnecting
@@ -255,7 +250,7 @@ bool APIConnection::send_disconnect_response(const DisconnectRequest &msg) {
// remote initiated disconnect_client // remote initiated disconnect_client
// don't close yet, we still need to send the disconnect response // don't close yet, we still need to send the disconnect response
// close will happen on next loop // close will happen on next loop
ESP_LOGD(TAG, "%s disconnected", this->get_client_combined_info().c_str()); ESP_LOGD(TAG, "%s (%s) disconnected", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
this->flags_.next_close = true; this->flags_.next_close = true;
DisconnectResponse resp; DisconnectResponse resp;
return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE); return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE);
@@ -1385,7 +1380,7 @@ void APIConnection::complete_authentication_() {
} }
this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED); this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED);
ESP_LOGD(TAG, "%s connected", this->get_client_combined_info().c_str()); ESP_LOGD(TAG, "%s (%s) connected", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER #ifdef USE_API_CLIENT_CONNECTED_TRIGGER
this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->client_info_.peername); this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->client_info_.peername);
#endif #endif
@@ -1394,6 +1389,11 @@ void APIConnection::complete_authentication_() {
this->send_time_request(); this->send_time_request();
} }
#endif #endif
#ifdef USE_ZWAVE_PROXY
if (zwave_proxy::global_zwave_proxy != nullptr) {
zwave_proxy::global_zwave_proxy->api_connection_authenticated(this);
}
#endif
} }
bool APIConnection::send_hello_response(const HelloRequest &msg) { bool APIConnection::send_hello_response(const HelloRequest &msg) {
@@ -1586,8 +1586,7 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
delay(0); delay(0);
APIError err = this->helper_->loop(); APIError err = this->helper_->loop();
if (err != APIError::OK) { if (err != APIError::OK) {
on_fatal_error(); this->fatal_error_with_log_(LOG_STR("Socket operation failed"), err);
this->log_socket_operation_failed_(err);
return false; return false;
} }
if (this->helper_->can_write_without_blocking()) if (this->helper_->can_write_without_blocking())
@@ -1606,8 +1605,7 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
if (err == APIError::WOULD_BLOCK) if (err == APIError::WOULD_BLOCK)
return false; return false;
if (err != APIError::OK) { if (err != APIError::OK) {
on_fatal_error(); this->fatal_error_with_log_(LOG_STR("Packet write failed"), err);
this->log_warning_(LOG_STR("Packet write failed"), err);
return false; return false;
} }
// Do not set last_traffic_ on send // Do not set last_traffic_ on send
@@ -1616,12 +1614,12 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
#ifdef USE_API_PASSWORD #ifdef USE_API_PASSWORD
void APIConnection::on_unauthenticated_access() { void APIConnection::on_unauthenticated_access() {
this->on_fatal_error(); this->on_fatal_error();
ESP_LOGD(TAG, "%s access without authentication", this->get_client_combined_info().c_str()); ESP_LOGD(TAG, "%s (%s) no authentication", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
} }
#endif #endif
void APIConnection::on_no_setup_connection() { void APIConnection::on_no_setup_connection() {
this->on_fatal_error(); this->on_fatal_error();
ESP_LOGD(TAG, "%s access without full connection", this->get_client_combined_info().c_str()); ESP_LOGD(TAG, "%s (%s) no connection setup", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
} }
void APIConnection::on_fatal_error() { void APIConnection::on_fatal_error() {
this->helper_->close(); this->helper_->close();
@@ -1793,8 +1791,7 @@ void APIConnection::process_batch_() {
APIError err = this->helper_->write_protobuf_packets(ProtoWriteBuffer{&shared_buf}, APIError err = this->helper_->write_protobuf_packets(ProtoWriteBuffer{&shared_buf},
std::span<const PacketInfo>(packet_info, packet_count)); std::span<const PacketInfo>(packet_info, packet_count));
if (err != APIError::OK && err != APIError::WOULD_BLOCK) { if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
on_fatal_error(); this->fatal_error_with_log_(LOG_STR("Batch write failed"), err);
this->log_warning_(LOG_STR("Batch write failed"), err);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@@ -1873,12 +1870,8 @@ void APIConnection::process_state_subscriptions_() {
#endif // USE_API_HOMEASSISTANT_STATES #endif // USE_API_HOMEASSISTANT_STATES
void APIConnection::log_warning_(const LogString *message, APIError err) { void APIConnection::log_warning_(const LogString *message, APIError err) {
ESP_LOGW(TAG, "%s: %s %s errno=%d", this->get_client_combined_info().c_str(), LOG_STR_ARG(message), ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->client_info_.name.c_str(), this->client_info_.peername.c_str(),
LOG_STR_ARG(api_error_to_logstr(err)), errno); LOG_STR_ARG(message), LOG_STR_ARG(api_error_to_logstr(err)), errno);
}
void APIConnection::log_socket_operation_failed_(APIError err) {
this->log_warning_(LOG_STR("Socket operation failed"), err);
} }
} // namespace esphome::api } // namespace esphome::api

View File

@@ -19,14 +19,6 @@ namespace esphome::api {
struct ClientInfo { struct ClientInfo {
std::string name; // Client name from Hello message std::string name; // Client name from Hello message
std::string peername; // IP:port from socket std::string peername; // IP:port from socket
std::string get_combined_info() const {
if (name == peername) {
// Before Hello message, both are the same
return name;
}
return name + " (" + peername + ")";
}
}; };
// Keepalive timeout in milliseconds // Keepalive timeout in milliseconds
@@ -281,7 +273,8 @@ class APIConnection final : public APIServerConnection {
bool try_to_clear_buffer(bool log_out_of_space); bool try_to_clear_buffer(bool log_out_of_space);
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override; bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override;
std::string get_client_combined_info() const { return this->client_info_.get_combined_info(); } const std::string &get_name() const { return this->client_info_.name; }
const std::string &get_peername() const { return this->client_info_.peername; }
protected: protected:
// Helper function to handle authentication completion // Helper function to handle authentication completion
@@ -742,8 +735,11 @@ class APIConnection final : public APIServerConnection {
// Helper function to log API errors with errno // Helper function to log API errors with errno
void log_warning_(const LogString *message, APIError err); void log_warning_(const LogString *message, APIError err);
// Specific helper for duplicated error message // Helper to handle fatal errors with logging
void log_socket_operation_failed_(APIError err); inline void fatal_error_with_log_(const LogString *message, APIError err) {
this->on_fatal_error();
this->log_warning_(message, err);
}
}; };
} // namespace esphome::api } // namespace esphome::api

View File

@@ -13,7 +13,8 @@ namespace esphome::api {
static const char *const TAG = "api.frame_helper"; static const char *const TAG = "api.frame_helper";
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__) #define HELPER_LOG(msg, ...) \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
#ifdef HELPER_LOG_PACKETS #ifdef HELPER_LOG_PACKETS
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str()) #define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
@@ -80,7 +81,7 @@ const LogString *api_error_to_logstr(APIError err) {
// Default implementation for loop - handles sending buffered data // Default implementation for loop - handles sending buffered data
APIError APIFrameHelper::loop() { APIError APIFrameHelper::loop() {
if (!this->tx_buf_.empty()) { if (this->tx_buf_count_ > 0) {
APIError err = try_send_tx_buf_(); APIError err = try_send_tx_buf_();
if (err != APIError::OK && err != APIError::WOULD_BLOCK) { if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
return err; return err;
@@ -102,9 +103,20 @@ APIError APIFrameHelper::handle_socket_write_error_() {
// Helper method to buffer data from IOVs // Helper method to buffer data from IOVs
void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len,
uint16_t offset) { uint16_t offset) {
SendBuffer buffer; // Check if queue is full
buffer.size = total_write_len - offset; if (this->tx_buf_count_ >= API_MAX_SEND_QUEUE) {
buffer.data = std::make_unique<uint8_t[]>(buffer.size); HELPER_LOG("Send queue full (%u buffers), dropping connection", this->tx_buf_count_);
this->state_ = State::FAILED;
return;
}
uint16_t buffer_size = total_write_len - offset;
auto &buffer = this->tx_buf_[this->tx_buf_tail_];
buffer = std::make_unique<SendBuffer>(SendBuffer{
.data = std::make_unique<uint8_t[]>(buffer_size),
.size = buffer_size,
.offset = 0,
});
uint16_t to_skip = offset; uint16_t to_skip = offset;
uint16_t write_pos = 0; uint16_t write_pos = 0;
@@ -117,12 +129,15 @@ void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt,
// Include this segment (partially or fully) // Include this segment (partially or fully)
const uint8_t *src = reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_skip; const uint8_t *src = reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_skip;
uint16_t len = static_cast<uint16_t>(iov[i].iov_len) - to_skip; uint16_t len = static_cast<uint16_t>(iov[i].iov_len) - to_skip;
std::memcpy(buffer.data.get() + write_pos, src, len); std::memcpy(buffer->data.get() + write_pos, src, len);
write_pos += len; write_pos += len;
to_skip = 0; to_skip = 0;
} }
} }
this->tx_buf_.push_back(std::move(buffer));
// Update circular buffer tracking
this->tx_buf_tail_ = (this->tx_buf_tail_ + 1) % API_MAX_SEND_QUEUE;
this->tx_buf_count_++;
} }
// This method writes data to socket or buffers it // This method writes data to socket or buffers it
@@ -140,7 +155,7 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_
#endif #endif
// Try to send any existing buffered data first if there is any // Try to send any existing buffered data first if there is any
if (!this->tx_buf_.empty()) { if (this->tx_buf_count_ > 0) {
APIError send_result = try_send_tx_buf_(); APIError send_result = try_send_tx_buf_();
// If real error occurred (not just WOULD_BLOCK), return it // If real error occurred (not just WOULD_BLOCK), return it
if (send_result != APIError::OK && send_result != APIError::WOULD_BLOCK) { if (send_result != APIError::OK && send_result != APIError::WOULD_BLOCK) {
@@ -149,7 +164,7 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_
// If there is still data in the buffer, we can't send, buffer // If there is still data in the buffer, we can't send, buffer
// the new data and return // the new data and return
if (!this->tx_buf_.empty()) { if (this->tx_buf_count_ > 0) {
this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0); this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0);
return APIError::OK; // Success, data buffered return APIError::OK; // Success, data buffered
} }
@@ -177,32 +192,31 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_
} }
// Common implementation for trying to send buffered data // Common implementation for trying to send buffered data
// IMPORTANT: Caller MUST ensure tx_buf_ is not empty before calling this method // IMPORTANT: Caller MUST ensure tx_buf_count_ > 0 before calling this method
APIError APIFrameHelper::try_send_tx_buf_() { APIError APIFrameHelper::try_send_tx_buf_() {
// Try to send from tx_buf - we assume it's not empty as it's the caller's responsibility to check // Try to send from tx_buf - we assume it's not empty as it's the caller's responsibility to check
bool tx_buf_empty = false; while (this->tx_buf_count_ > 0) {
while (!tx_buf_empty) {
// Get the first buffer in the queue // Get the first buffer in the queue
SendBuffer &front_buffer = this->tx_buf_.front(); SendBuffer *front_buffer = this->tx_buf_[this->tx_buf_head_].get();
// Try to send the remaining data in this buffer // Try to send the remaining data in this buffer
ssize_t sent = this->socket_->write(front_buffer.current_data(), front_buffer.remaining()); ssize_t sent = this->socket_->write(front_buffer->current_data(), front_buffer->remaining());
if (sent == -1) { if (sent == -1) {
return this->handle_socket_write_error_(); return this->handle_socket_write_error_();
} else if (sent == 0) { } else if (sent == 0) {
// Nothing sent but not an error // Nothing sent but not an error
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
} else if (static_cast<uint16_t>(sent) < front_buffer.remaining()) { } else if (static_cast<uint16_t>(sent) < front_buffer->remaining()) {
// Partially sent, update offset // Partially sent, update offset
// Cast to ensure no overflow issues with uint16_t // Cast to ensure no overflow issues with uint16_t
front_buffer.offset += static_cast<uint16_t>(sent); front_buffer->offset += static_cast<uint16_t>(sent);
return APIError::WOULD_BLOCK; // Stop processing more buffers if we couldn't send a complete buffer return APIError::WOULD_BLOCK; // Stop processing more buffers if we couldn't send a complete buffer
} else { } else {
// Buffer completely sent, remove it from the queue // Buffer completely sent, remove it from the queue
this->tx_buf_.pop_front(); this->tx_buf_[this->tx_buf_head_].reset();
// Update empty status for the loop condition this->tx_buf_head_ = (this->tx_buf_head_ + 1) % API_MAX_SEND_QUEUE;
tx_buf_empty = this->tx_buf_.empty(); this->tx_buf_count_--;
// Continue loop to try sending the next buffer // Continue loop to try sending the next buffer
} }
} }

View File

@@ -1,7 +1,8 @@
#pragma once #pragma once
#include <array>
#include <cstdint> #include <cstdint>
#include <deque>
#include <limits> #include <limits>
#include <memory>
#include <span> #include <span>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -79,7 +80,7 @@ class APIFrameHelper {
virtual APIError init() = 0; virtual APIError init() = 0;
virtual APIError loop(); virtual APIError loop();
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0; virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
bool can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); } bool can_write_without_blocking() { return this->state_ == State::DATA && this->tx_buf_count_ == 0; }
std::string getpeername() { return socket_->getpeername(); } std::string getpeername() { return socket_->getpeername(); }
int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); } int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); }
APIError close() { APIError close() {
@@ -161,7 +162,7 @@ class APIFrameHelper {
}; };
// Containers (size varies, but typically 12+ bytes on 32-bit) // Containers (size varies, but typically 12+ bytes on 32-bit)
std::deque<SendBuffer> tx_buf_; std::array<std::unique_ptr<SendBuffer>, API_MAX_SEND_QUEUE> tx_buf_;
std::vector<struct iovec> reusable_iovs_; std::vector<struct iovec> reusable_iovs_;
std::vector<uint8_t> rx_buf_; std::vector<uint8_t> rx_buf_;
@@ -174,7 +175,10 @@ class APIFrameHelper {
State state_{State::INITIALIZE}; State state_{State::INITIALIZE};
uint8_t frame_header_padding_{0}; uint8_t frame_header_padding_{0};
uint8_t frame_footer_size_{0}; uint8_t frame_footer_size_{0};
// 5 bytes total, 3 bytes padding uint8_t tx_buf_head_{0};
uint8_t tx_buf_tail_{0};
uint8_t tx_buf_count_{0};
// 8 bytes total, 0 bytes padding
// Common initialization for both plaintext and noise protocols // Common initialization for both plaintext and noise protocols
APIError init_common_(); APIError init_common_();

View File

@@ -24,7 +24,8 @@ static const char *const PROLOGUE_INIT = "NoiseAPIInit";
#endif #endif
static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit") static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__) #define HELPER_LOG(msg, ...) \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
#ifdef HELPER_LOG_PACKETS #ifdef HELPER_LOG_PACKETS
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str()) #define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())

View File

@@ -18,7 +18,8 @@ namespace esphome::api {
static const char *const TAG = "api.plaintext"; static const char *const TAG = "api.plaintext";
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__) #define HELPER_LOG(msg, ...) \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
#ifdef HELPER_LOG_PACKETS #ifdef HELPER_LOG_PACKETS
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str()) #define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())

View File

@@ -181,7 +181,8 @@ void APIServer::loop() {
// Network is down - disconnect all clients // Network is down - disconnect all clients
for (auto &client : this->clients_) { for (auto &client : this->clients_) {
client->on_fatal_error(); client->on_fatal_error();
ESP_LOGW(TAG, "%s: Network down; disconnect", client->get_client_combined_info().c_str()); ESP_LOGW(TAG, "%s (%s): Network down; disconnect", client->client_info_.name.c_str(),
client->client_info_.peername.c_str());
} }
// Continue to process and clean up the clients below // Continue to process and clean up the clients below
} }

View File

@@ -55,7 +55,7 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
protected: protected:
virtual void execute(Ts... x) = 0; virtual void execute(Ts... x) = 0;
template<int... S> void execute_(std::vector<ExecuteServiceArgument> args, seq<S...> type) { template<int... S> void execute_(const std::vector<ExecuteServiceArgument> &args, seq<S...> type) {
this->execute((get_execute_arg_value<Ts>(args[S]))...); this->execute((get_execute_arg_value<Ts>(args[S]))...);
} }

View File

@@ -57,7 +57,7 @@ const char *audio_file_type_to_string(AudioFileType file_type) {
void scale_audio_samples(const int16_t *audio_samples, int16_t *output_buffer, int16_t scale_factor, void scale_audio_samples(const int16_t *audio_samples, int16_t *output_buffer, int16_t scale_factor,
size_t samples_to_scale) { size_t samples_to_scale) {
// Note the assembly dsps_mulc function has audio glitches if the input and output buffers are the same. // Note the assembly dsps_mulc function has audio glitches if the input and output buffers are the same.
for (int i = 0; i < samples_to_scale; i++) { for (size_t i = 0; i < samples_to_scale; i++) {
int32_t acc = (int32_t) audio_samples[i] * (int32_t) scale_factor; int32_t acc = (int32_t) audio_samples[i] * (int32_t) scale_factor;
output_buffer[i] = (int16_t) (acc >> 15); output_buffer[i] = (int16_t) (acc >> 15);
} }

View File

@@ -97,10 +97,10 @@ void BL0906::handle_actions_() {
return; return;
} }
ActionCallbackFuncPtr ptr_func = nullptr; ActionCallbackFuncPtr ptr_func = nullptr;
for (int i = 0; i < this->action_queue_.size(); i++) { for (size_t i = 0; i < this->action_queue_.size(); i++) {
ptr_func = this->action_queue_[i]; ptr_func = this->action_queue_[i];
if (ptr_func) { if (ptr_func) {
ESP_LOGI(TAG, "HandleActionCallback[%d]", i); ESP_LOGI(TAG, "HandleActionCallback[%zu]", i);
(this->*ptr_func)(); (this->*ptr_func)();
} }
} }

View File

@@ -51,7 +51,7 @@ void BL0942::loop() {
if (!avail) { if (!avail) {
return; return;
} }
if (avail < sizeof(buffer)) { if (static_cast<size_t>(avail) < sizeof(buffer)) {
if (!this->rx_start_) { if (!this->rx_start_) {
this->rx_start_ = millis(); this->rx_start_ = millis();
} else if (millis() > this->rx_start_ + PKT_TIMEOUT_MS) { } else if (millis() > this->rx_start_ + PKT_TIMEOUT_MS) {
@@ -148,7 +148,7 @@ void BL0942::setup() {
this->write_reg_(BL0942_REG_USR_WRPROT, 0); this->write_reg_(BL0942_REG_USR_WRPROT, 0);
if (this->read_reg_(BL0942_REG_MODE) != mode) if (static_cast<uint32_t>(this->read_reg_(BL0942_REG_MODE)) != mode)
this->status_set_warning(LOG_STR("BL0942 setup failed!")); this->status_set_warning(LOG_STR("BL0942 setup failed!"));
this->flush(); this->flush();

View File

@@ -116,7 +116,7 @@ CONFIG_SCHEMA = cv.All(
) )
.extend(cv.COMPONENT_SCHEMA) .extend(cv.COMPONENT_SCHEMA)
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA), .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA),
esp32_ble_tracker.consume_connection_slots(1, "ble_client"), esp32_ble.consume_connection_slots(1, "ble_client"),
) )
CONF_BLE_CLIENT_ID = "ble_client_id" CONF_BLE_CLIENT_ID = "ble_client_id"

View File

@@ -42,9 +42,7 @@ def validate_connections(config):
) )
elif config[CONF_ACTIVE]: elif config[CONF_ACTIVE]:
connection_slots: int = config[CONF_CONNECTION_SLOTS] connection_slots: int = config[CONF_CONNECTION_SLOTS]
esp32_ble_tracker.consume_connection_slots(connection_slots, "bluetooth_proxy")( esp32_ble.consume_connection_slots(connection_slots, "bluetooth_proxy")(config)
config
)
return { return {
**config, **config,
@@ -65,11 +63,11 @@ CONFIG_SCHEMA = cv.All(
default=DEFAULT_CONNECTION_SLOTS, default=DEFAULT_CONNECTION_SLOTS,
): cv.All( ): cv.All(
cv.positive_int, cv.positive_int,
cv.Range(min=1, max=esp32_ble_tracker.IDF_MAX_CONNECTIONS), cv.Range(min=1, max=esp32_ble.IDF_MAX_CONNECTIONS),
), ),
cv.Optional(CONF_CONNECTIONS): cv.All( cv.Optional(CONF_CONNECTIONS): cv.All(
cv.ensure_list(CONNECTION_SCHEMA), cv.ensure_list(CONNECTION_SCHEMA),
cv.Length(min=1, max=esp32_ble_tracker.IDF_MAX_CONNECTIONS), cv.Length(min=1, max=esp32_ble.IDF_MAX_CONNECTIONS),
), ),
} }
) )

View File

@@ -11,14 +11,14 @@ namespace captive_portal {
static const char *const TAG = "captive_portal"; static const char *const TAG = "captive_portal";
void CaptivePortal::handle_config(AsyncWebServerRequest *request) { void CaptivePortal::handle_config(AsyncWebServerRequest *request) {
AsyncResponseStream *stream = request->beginResponseStream(F("application/json")); AsyncResponseStream *stream = request->beginResponseStream(ESPHOME_F("application/json"));
stream->addHeader(F("cache-control"), F("public, max-age=0, must-revalidate")); stream->addHeader(ESPHOME_F("cache-control"), ESPHOME_F("public, max-age=0, must-revalidate"));
#ifdef USE_ESP8266 #ifdef USE_ESP8266
stream->print(F("{\"mac\":\"")); stream->print(ESPHOME_F("{\"mac\":\""));
stream->print(get_mac_address_pretty().c_str()); stream->print(get_mac_address_pretty().c_str());
stream->print(F("\",\"name\":\"")); stream->print(ESPHOME_F("\",\"name\":\""));
stream->print(App.get_name().c_str()); stream->print(App.get_name().c_str());
stream->print(F("\",\"aps\":[{}")); stream->print(ESPHOME_F("\",\"aps\":[{}"));
#else #else
stream->printf(R"({"mac":"%s","name":"%s","aps":[{})", get_mac_address_pretty().c_str(), App.get_name().c_str()); stream->printf(R"({"mac":"%s","name":"%s","aps":[{})", get_mac_address_pretty().c_str(), App.get_name().c_str());
#endif #endif
@@ -29,19 +29,19 @@ void CaptivePortal::handle_config(AsyncWebServerRequest *request) {
// Assumes no " in ssid, possible unicode isses? // Assumes no " in ssid, possible unicode isses?
#ifdef USE_ESP8266 #ifdef USE_ESP8266
stream->print(F(",{\"ssid\":\"")); stream->print(ESPHOME_F(",{\"ssid\":\""));
stream->print(scan.get_ssid().c_str()); stream->print(scan.get_ssid().c_str());
stream->print(F("\",\"rssi\":")); stream->print(ESPHOME_F("\",\"rssi\":"));
stream->print(scan.get_rssi()); stream->print(scan.get_rssi());
stream->print(F(",\"lock\":")); stream->print(ESPHOME_F(",\"lock\":"));
stream->print(scan.get_with_auth()); stream->print(scan.get_with_auth());
stream->print(F("}")); stream->print(ESPHOME_F("}"));
#else #else
stream->printf(R"(,{"ssid":"%s","rssi":%d,"lock":%d})", scan.get_ssid().c_str(), scan.get_rssi(), stream->printf(R"(,{"ssid":"%s","rssi":%d,"lock":%d})", scan.get_ssid().c_str(), scan.get_rssi(),
scan.get_with_auth()); scan.get_with_auth());
#endif #endif
} }
stream->print(F("]}")); stream->print(ESPHOME_F("]}"));
request->send(stream); request->send(stream);
} }
void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
@@ -52,7 +52,7 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str()); ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str());
wifi::global_wifi_component->save_wifi_sta(ssid, psk); wifi::global_wifi_component->save_wifi_sta(ssid, psk);
wifi::global_wifi_component->start_scanning(); wifi::global_wifi_component->start_scanning();
request->redirect(F("/?save")); request->redirect(ESPHOME_F("/?save"));
} }
void CaptivePortal::setup() { void CaptivePortal::setup() {
@@ -75,7 +75,7 @@ void CaptivePortal::start() {
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
this->dns_server_ = make_unique<DNSServer>(); this->dns_server_ = make_unique<DNSServer>();
this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError); this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
this->dns_server_->start(53, F("*"), ip); this->dns_server_->start(53, ESPHOME_F("*"), ip);
#endif #endif
this->initialized_ = true; this->initialized_ = true;
@@ -88,10 +88,10 @@ void CaptivePortal::start() {
} }
void CaptivePortal::handleRequest(AsyncWebServerRequest *req) { void CaptivePortal::handleRequest(AsyncWebServerRequest *req) {
if (req->url() == F("/config.json")) { if (req->url() == ESPHOME_F("/config.json")) {
this->handle_config(req); this->handle_config(req);
return; return;
} else if (req->url() == F("/wifisave")) { } else if (req->url() == ESPHOME_F("/wifisave")) {
this->handle_wifisave(req); this->handle_wifisave(req);
return; return;
} }
@@ -100,11 +100,11 @@ void CaptivePortal::handleRequest(AsyncWebServerRequest *req) {
// This includes OS captive portal detection endpoints which will trigger // This includes OS captive portal detection endpoints which will trigger
// the captive portal when they don't receive their expected responses // the captive portal when they don't receive their expected responses
#ifndef USE_ESP8266 #ifndef USE_ESP8266
auto *response = req->beginResponse(200, F("text/html"), INDEX_GZ, sizeof(INDEX_GZ)); auto *response = req->beginResponse(200, ESPHOME_F("text/html"), INDEX_GZ, sizeof(INDEX_GZ));
#else #else
auto *response = req->beginResponse_P(200, F("text/html"), INDEX_GZ, sizeof(INDEX_GZ)); auto *response = req->beginResponse_P(200, ESPHOME_F("text/html"), INDEX_GZ, sizeof(INDEX_GZ));
#endif #endif
response->addHeader(F("Content-Encoding"), F("gzip")); response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("gzip"));
req->send(response); req->send(response);
} }

View File

@@ -13,7 +13,7 @@ static const uint8_t C_M1106_CMD_SET_CO2_CALIB_RESPONSE[4] = {0x16, 0x01, 0x03,
uint8_t cm1106_checksum(const uint8_t *response, size_t len) { uint8_t cm1106_checksum(const uint8_t *response, size_t len) {
uint8_t crc = 0; uint8_t crc = 0;
for (int i = 0; i < len - 1; i++) { for (size_t i = 0; i < len - 1; i++) {
crc -= response[i]; crc -= response[i];
} }
return crc; return crc;

View File

@@ -11,7 +11,7 @@ void CopyLock::setup() {
traits.set_assumed_state(source_->traits.get_assumed_state()); traits.set_assumed_state(source_->traits.get_assumed_state());
traits.set_requires_code(source_->traits.get_requires_code()); traits.set_requires_code(source_->traits.get_requires_code());
traits.set_supported_states(source_->traits.get_supported_states()); traits.set_supported_states_mask(source_->traits.get_supported_states_mask());
traits.set_supports_open(source_->traits.get_supports_open()); traits.set_supports_open(source_->traits.get_supports_open());
this->publish_state(source_->state); this->publish_state(source_->state);

View File

@@ -26,7 +26,7 @@ void DaikinArcClimate::transmit_query_() {
uint8_t remote_header[8] = {0x11, 0xDA, 0x27, 0x00, 0x84, 0x87, 0x20, 0x00}; uint8_t remote_header[8] = {0x11, 0xDA, 0x27, 0x00, 0x84, 0x87, 0x20, 0x00};
// Calculate checksum // Calculate checksum
for (int i = 0; i < sizeof(remote_header) - 1; i++) { for (size_t i = 0; i < sizeof(remote_header) - 1; i++) {
remote_header[sizeof(remote_header) - 1] += remote_header[i]; remote_header[sizeof(remote_header) - 1] += remote_header[i];
} }
@@ -102,7 +102,7 @@ void DaikinArcClimate::transmit_state() {
remote_state[9] = fan_speed & 0xff; remote_state[9] = fan_speed & 0xff;
// Calculate checksum // Calculate checksum
for (int i = 0; i < sizeof(remote_header) - 1; i++) { for (size_t i = 0; i < sizeof(remote_header) - 1; i++) {
remote_header[sizeof(remote_header) - 1] += remote_header[i]; remote_header[sizeof(remote_header) - 1] += remote_header[i];
} }
@@ -350,7 +350,7 @@ bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) {
bool valid_daikin_frame = false; bool valid_daikin_frame = false;
if (data.expect_item(DAIKIN_HEADER_MARK, DAIKIN_HEADER_SPACE)) { if (data.expect_item(DAIKIN_HEADER_MARK, DAIKIN_HEADER_SPACE)) {
valid_daikin_frame = true; valid_daikin_frame = true;
int bytes_count = data.size() / 2 / 8; size_t bytes_count = data.size() / 2 / 8;
std::unique_ptr<char[]> buf(new char[bytes_count * 3 + 1]); std::unique_ptr<char[]> buf(new char[bytes_count * 3 + 1]);
buf[0] = '\0'; buf[0] = '\0';
for (size_t i = 0; i < bytes_count; i++) { for (size_t i = 0; i < bytes_count; i++) {
@@ -370,7 +370,7 @@ bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) {
if (!valid_daikin_frame) { if (!valid_daikin_frame) {
char sbuf[16 * 10 + 1]; char sbuf[16 * 10 + 1];
sbuf[0] = '\0'; sbuf[0] = '\0';
for (size_t j = 0; j < data.size(); j++) { for (size_t j = 0; j < static_cast<size_t>(data.size()); j++) {
if ((j - 2) % 16 == 0) { if ((j - 2) % 16 == 0) {
if (j > 0) { if (j > 0) {
ESP_LOGD(TAG, "DATA %04x: %s", (j - 16 > 0xffff ? 0 : j - 16), sbuf); ESP_LOGD(TAG, "DATA %04x: %s", (j - 16 > 0xffff ? 0 : j - 16), sbuf);
@@ -380,19 +380,26 @@ bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) {
char type_ch = ' '; char type_ch = ' ';
// debug_tolerance = 25% // debug_tolerance = 25%
if (DAIKIN_DBG_LOWER(DAIKIN_ARC_PRE_MARK) <= data[j] && data[j] <= DAIKIN_DBG_UPPER(DAIKIN_ARC_PRE_MARK)) if (static_cast<int32_t>(DAIKIN_DBG_LOWER(DAIKIN_ARC_PRE_MARK)) <= data[j] &&
data[j] <= static_cast<int32_t>(DAIKIN_DBG_UPPER(DAIKIN_ARC_PRE_MARK)))
type_ch = 'P'; type_ch = 'P';
if (DAIKIN_DBG_LOWER(DAIKIN_ARC_PRE_SPACE) <= -data[j] && -data[j] <= DAIKIN_DBG_UPPER(DAIKIN_ARC_PRE_SPACE)) if (static_cast<int32_t>(DAIKIN_DBG_LOWER(DAIKIN_ARC_PRE_SPACE)) <= -data[j] &&
-data[j] <= static_cast<int32_t>(DAIKIN_DBG_UPPER(DAIKIN_ARC_PRE_SPACE)))
type_ch = 'a'; type_ch = 'a';
if (DAIKIN_DBG_LOWER(DAIKIN_HEADER_MARK) <= data[j] && data[j] <= DAIKIN_DBG_UPPER(DAIKIN_HEADER_MARK)) if (static_cast<int32_t>(DAIKIN_DBG_LOWER(DAIKIN_HEADER_MARK)) <= data[j] &&
data[j] <= static_cast<int32_t>(DAIKIN_DBG_UPPER(DAIKIN_HEADER_MARK)))
type_ch = 'H'; type_ch = 'H';
if (DAIKIN_DBG_LOWER(DAIKIN_HEADER_SPACE) <= -data[j] && -data[j] <= DAIKIN_DBG_UPPER(DAIKIN_HEADER_SPACE)) if (static_cast<int32_t>(DAIKIN_DBG_LOWER(DAIKIN_HEADER_SPACE)) <= -data[j] &&
-data[j] <= static_cast<int32_t>(DAIKIN_DBG_UPPER(DAIKIN_HEADER_SPACE)))
type_ch = 'h'; type_ch = 'h';
if (DAIKIN_DBG_LOWER(DAIKIN_BIT_MARK) <= data[j] && data[j] <= DAIKIN_DBG_UPPER(DAIKIN_BIT_MARK)) if (static_cast<int32_t>(DAIKIN_DBG_LOWER(DAIKIN_BIT_MARK)) <= data[j] &&
data[j] <= static_cast<int32_t>(DAIKIN_DBG_UPPER(DAIKIN_BIT_MARK)))
type_ch = 'B'; type_ch = 'B';
if (DAIKIN_DBG_LOWER(DAIKIN_ONE_SPACE) <= -data[j] && -data[j] <= DAIKIN_DBG_UPPER(DAIKIN_ONE_SPACE)) if (static_cast<int32_t>(DAIKIN_DBG_LOWER(DAIKIN_ONE_SPACE)) <= -data[j] &&
-data[j] <= static_cast<int32_t>(DAIKIN_DBG_UPPER(DAIKIN_ONE_SPACE)))
type_ch = '1'; type_ch = '1';
if (DAIKIN_DBG_LOWER(DAIKIN_ZERO_SPACE) <= -data[j] && -data[j] <= DAIKIN_DBG_UPPER(DAIKIN_ZERO_SPACE)) if (static_cast<int32_t>(DAIKIN_DBG_LOWER(DAIKIN_ZERO_SPACE)) <= -data[j] &&
-data[j] <= static_cast<int32_t>(DAIKIN_DBG_UPPER(DAIKIN_ZERO_SPACE)))
type_ch = '0'; type_ch = '0';
if (abs(data[j]) > 100000) { if (abs(data[j]) > 100000) {
@@ -400,7 +407,7 @@ bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) {
} else { } else {
sprintf(sbuf, "%s%-5d[%c] ", sbuf, (int) (round(data[j] / 10.) * 10), type_ch); sprintf(sbuf, "%s%-5d[%c] ", sbuf, (int) (round(data[j] / 10.) * 10), type_ch);
} }
if (j == data.size() - 1) { if (j + 1 == static_cast<size_t>(data.size())) {
ESP_LOGD(TAG, "DATA %04x: %s", (j - 8 > 0xffff ? 0 : j - 8), sbuf); ESP_LOGD(TAG, "DATA %04x: %s", (j - 8 > 0xffff ? 0 : j - 8), sbuf);
} }
} }

View File

@@ -97,12 +97,12 @@ bool ES7210::set_mic_gain(float mic_gain) {
} }
bool ES7210::configure_sample_rate_() { bool ES7210::configure_sample_rate_() {
int mclk_fre = this->sample_rate_ * MCLK_DIV_FRE; uint32_t mclk_fre = this->sample_rate_ * MCLK_DIV_FRE;
int coeff = -1; int coeff = -1;
for (int i = 0; i < (sizeof(ES7210_COEFFICIENTS) / sizeof(ES7210_COEFFICIENTS[0])); ++i) { for (size_t i = 0; i < (sizeof(ES7210_COEFFICIENTS) / sizeof(ES7210_COEFFICIENTS[0])); ++i) {
if (ES7210_COEFFICIENTS[i].lrclk == this->sample_rate_ && ES7210_COEFFICIENTS[i].mclk == mclk_fre) if (ES7210_COEFFICIENTS[i].lrclk == this->sample_rate_ && ES7210_COEFFICIENTS[i].mclk == mclk_fre)
coeff = i; coeff = static_cast<int>(i);
} }
if (coeff >= 0) { if (coeff >= 0) {

View File

@@ -296,14 +296,9 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
return f"pioarduino/framework-arduinoespressif32@https://github.com/espressif/arduino-esp32/releases/download/{str(ver)}/esp32-{str(ver)}.zip" return f"pioarduino/framework-arduinoespressif32@https://github.com/espressif/arduino-esp32/releases/download/{str(ver)}/esp32-{str(ver)}.zip"
def _format_framework_espidf_version( def _format_framework_espidf_version(ver: cv.Version, release: str) -> str:
ver: cv.Version, release: str, for_platformio: bool # format the given espidf (https://github.com/pioarduino/esp-idf/releases) version to
) -> str:
# format the given arduino (https://github.com/espressif/esp-idf/releases) version to
# a PIO platformio/framework-espidf value # a PIO platformio/framework-espidf value
# List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
if for_platformio:
return f"platformio/framework-espidf@~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
if release: if release:
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}.{release}/esp-idf-v{str(ver)}.zip" return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}.{release}/esp-idf-v{str(ver)}.zip"
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.zip" return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.zip"
@@ -317,157 +312,108 @@ def _format_framework_espidf_version(
# The default/recommended arduino framework version # The default/recommended arduino framework version
# - https://github.com/espressif/arduino-esp32/releases # - https://github.com/espressif/arduino-esp32/releases
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 2, 1) ARDUINO_FRAMEWORK_VERSION_LOOKUP = {
# The platform-espressif32 version to use for arduino frameworks "recommended": cv.Version(3, 2, 1),
# - https://github.com/pioarduino/platform-espressif32/releases "latest": cv.Version(3, 3, 1),
ARDUINO_PLATFORM_VERSION = cv.Version(54, 3, 21, "2") "dev": cv.Version(3, 3, 1),
}
ARDUINO_PLATFORM_VERSION_LOOKUP = {
cv.Version(3, 3, 1): cv.Version(55, 3, 31),
cv.Version(3, 3, 0): cv.Version(55, 3, 30, "2"),
cv.Version(3, 2, 1): cv.Version(54, 3, 21, "2"),
cv.Version(3, 2, 0): cv.Version(54, 3, 20),
cv.Version(3, 1, 3): cv.Version(53, 3, 13),
cv.Version(3, 1, 2): cv.Version(53, 3, 12),
cv.Version(3, 1, 1): cv.Version(53, 3, 11),
cv.Version(3, 1, 0): cv.Version(53, 3, 10),
}
# The default/recommended esp-idf framework version # The default/recommended esp-idf framework version
# - https://github.com/espressif/esp-idf/releases # - https://github.com/espressif/esp-idf/releases
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf ESP_IDF_FRAMEWORK_VERSION_LOOKUP = {
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(5, 4, 2) "recommended": cv.Version(5, 4, 2),
# The platformio/espressif32 version to use for esp-idf frameworks "latest": cv.Version(5, 5, 1),
# - https://github.com/platformio/platform-espressif32/releases "dev": cv.Version(5, 5, 1),
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 }
ESP_IDF_PLATFORM_VERSION = cv.Version(54, 3, 21, "2") ESP_IDF_PLATFORM_VERSION_LOOKUP = {
cv.Version(5, 5, 1): cv.Version(55, 3, 31),
cv.Version(5, 5, 0): cv.Version(55, 3, 31),
cv.Version(5, 4, 2): cv.Version(54, 3, 21, "2"),
cv.Version(5, 4, 1): cv.Version(54, 3, 21, "2"),
cv.Version(5, 4, 0): cv.Version(54, 3, 21, "2"),
cv.Version(5, 3, 2): cv.Version(53, 3, 13),
cv.Version(5, 3, 1): cv.Version(53, 3, 13),
cv.Version(5, 3, 0): cv.Version(53, 3, 13),
cv.Version(5, 1, 6): cv.Version(51, 3, 7),
cv.Version(5, 1, 5): cv.Version(51, 3, 7),
}
# List based on https://registry.platformio.org/tools/platformio/framework-espidf/versions # The platform-espressif32 version
SUPPORTED_PLATFORMIO_ESP_IDF_5X = [ # - https://github.com/pioarduino/platform-espressif32/releases
cv.Version(5, 3, 1), PLATFORM_VERSION_LOOKUP = {
cv.Version(5, 3, 0), "recommended": cv.Version(54, 3, 21, "2"),
cv.Version(5, 2, 2), "latest": cv.Version(55, 3, 31),
cv.Version(5, 2, 1), "dev": "https://github.com/pioarduino/platform-espressif32.git#develop",
cv.Version(5, 1, 2), }
cv.Version(5, 1, 1),
cv.Version(5, 1, 0),
cv.Version(5, 0, 2),
cv.Version(5, 0, 1),
cv.Version(5, 0, 0),
]
# pioarduino versions that don't require a release number
# List based on https://github.com/pioarduino/esp-idf/releases
SUPPORTED_PIOARDUINO_ESP_IDF_5X = [
cv.Version(5, 5, 1),
cv.Version(5, 5, 0),
cv.Version(5, 4, 2),
cv.Version(5, 4, 1),
cv.Version(5, 4, 0),
cv.Version(5, 3, 3),
cv.Version(5, 3, 2),
cv.Version(5, 3, 1),
cv.Version(5, 3, 0),
cv.Version(5, 1, 5),
cv.Version(5, 1, 6),
]
def _check_versions(value): def _check_versions(value):
value = value.copy() value = value.copy()
if value[CONF_TYPE] == FRAMEWORK_ARDUINO:
lookups = {
"dev": (
cv.Version(3, 2, 1),
"https://github.com/espressif/arduino-esp32.git",
),
"latest": (cv.Version(3, 2, 1), None),
"recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
}
if value[CONF_VERSION] in lookups: if value[CONF_VERSION] in PLATFORM_VERSION_LOOKUP:
if CONF_SOURCE in value: if CONF_SOURCE in value or CONF_PLATFORM_VERSION in value:
raise cv.Invalid(
"Framework version needs to be explicitly specified when custom source is used."
)
version, source = lookups[value[CONF_VERSION]]
else:
version = cv.Version.parse(cv.version_number(value[CONF_VERSION]))
source = value.get(CONF_SOURCE, None)
value[CONF_VERSION] = str(version)
value[CONF_SOURCE] = source or _format_framework_arduino_version(version)
value[CONF_PLATFORM_VERSION] = value.get(
CONF_PLATFORM_VERSION,
_parse_platform_version(str(ARDUINO_PLATFORM_VERSION)),
)
if value[CONF_SOURCE].startswith("http"):
# prefix is necessary or platformio will complain with a cryptic error
value[CONF_SOURCE] = f"framework-arduinoespressif32@{value[CONF_SOURCE]}"
if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION:
_LOGGER.warning(
"The selected Arduino framework version is not the recommended one. "
"If there are connectivity or build issues please remove the manual version."
)
return value
lookups = {
"dev": (cv.Version(5, 4, 2), "https://github.com/espressif/esp-idf.git"),
"latest": (cv.Version(5, 2, 2), None),
"recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None),
}
if value[CONF_VERSION] in lookups:
if CONF_SOURCE in value:
raise cv.Invalid( raise cv.Invalid(
"Framework version needs to be explicitly specified when custom source is used." "Version needs to be explicitly set when a custom source or platform_version is used."
) )
version, source = lookups[value[CONF_VERSION]] platform_lookup = PLATFORM_VERSION_LOOKUP[value[CONF_VERSION]]
value[CONF_PLATFORM_VERSION] = _parse_platform_version(str(platform_lookup))
if value[CONF_TYPE] == FRAMEWORK_ARDUINO:
version = ARDUINO_FRAMEWORK_VERSION_LOOKUP[value[CONF_VERSION]]
else:
version = ESP_IDF_FRAMEWORK_VERSION_LOOKUP[value[CONF_VERSION]]
else: else:
version = cv.Version.parse(cv.version_number(value[CONF_VERSION])) version = cv.Version.parse(cv.version_number(value[CONF_VERSION]))
source = value.get(CONF_SOURCE, None)
if version < cv.Version(5, 0, 0):
raise cv.Invalid("Only ESP-IDF 5.0+ is supported.")
# flag this for later *before* we set value[CONF_PLATFORM_VERSION] below
has_platform_ver = CONF_PLATFORM_VERSION in value
value[CONF_PLATFORM_VERSION] = value.get(
CONF_PLATFORM_VERSION, _parse_platform_version(str(ESP_IDF_PLATFORM_VERSION))
)
if (
is_platformio := _platform_is_platformio(value[CONF_PLATFORM_VERSION])
) and version not in SUPPORTED_PLATFORMIO_ESP_IDF_5X:
raise cv.Invalid(
f"ESP-IDF {str(version)} not supported by platformio/espressif32"
)
if (
version in SUPPORTED_PLATFORMIO_ESP_IDF_5X
and version not in SUPPORTED_PIOARDUINO_ESP_IDF_5X
) and not has_platform_ver:
raise cv.Invalid(
f"ESP-IDF {value[CONF_VERSION]} may be supported by platformio/espressif32; please specify '{CONF_PLATFORM_VERSION}'"
)
if (
not is_platformio
and CONF_RELEASE not in value
and version not in SUPPORTED_PIOARDUINO_ESP_IDF_5X
):
raise cv.Invalid(
f"ESP-IDF {value[CONF_VERSION]} is not available with pioarduino; you may need to specify '{CONF_RELEASE}'"
)
value[CONF_VERSION] = str(version) value[CONF_VERSION] = str(version)
value[CONF_SOURCE] = source or _format_framework_espidf_version(
version, value.get(CONF_RELEASE, None), is_platformio
)
if value[CONF_SOURCE].startswith("http"): if value[CONF_TYPE] == FRAMEWORK_ARDUINO:
# prefix is necessary or platformio will complain with a cryptic error if version < cv.Version(3, 0, 0):
value[CONF_SOURCE] = f"framework-espidf@{value[CONF_SOURCE]}" raise cv.Invalid("Only Arduino 3.0+ is supported.")
recommended_version = ARDUINO_FRAMEWORK_VERSION_LOOKUP["recommended"]
platform_lookup = ARDUINO_PLATFORM_VERSION_LOOKUP.get(version)
value[CONF_SOURCE] = value.get(
CONF_SOURCE, _format_framework_arduino_version(version)
)
else:
if version < cv.Version(5, 0, 0):
raise cv.Invalid("Only ESP-IDF 5.0+ is supported.")
recommended_version = ESP_IDF_FRAMEWORK_VERSION_LOOKUP["recommended"]
platform_lookup = ESP_IDF_PLATFORM_VERSION_LOOKUP.get(version)
value[CONF_SOURCE] = value.get(
CONF_SOURCE,
_format_framework_espidf_version(version, value.get(CONF_RELEASE, None)),
)
if version != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION: if CONF_PLATFORM_VERSION not in value:
if platform_lookup is None:
raise cv.Invalid(
"Framework version not recognized; please specify platform_version"
)
value[CONF_PLATFORM_VERSION] = _parse_platform_version(str(platform_lookup))
if version != recommended_version:
_LOGGER.warning( _LOGGER.warning(
"The selected ESP-IDF framework version is not the recommended one. " "The selected framework version is not the recommended one. "
"If there are connectivity or build issues please remove the manual version."
)
if value[CONF_PLATFORM_VERSION] != _parse_platform_version(
str(PLATFORM_VERSION_LOOKUP["recommended"])
):
_LOGGER.warning(
"The selected platform version is not the recommended one. "
"If there are connectivity or build issues please remove the manual version." "If there are connectivity or build issues please remove the manual version."
) )
@@ -477,26 +423,14 @@ def _check_versions(value):
def _parse_platform_version(value): def _parse_platform_version(value):
try: try:
ver = cv.Version.parse(cv.version_number(value)) ver = cv.Version.parse(cv.version_number(value))
if ver.major >= 50: # a pioarduino version release = f"{ver.major}.{ver.minor:02d}.{ver.patch:02d}"
release = f"{ver.major}.{ver.minor:02d}.{ver.patch:02d}" if ver.extra:
if ver.extra: release += f"-{ver.extra}"
release += f"-{ver.extra}" return f"https://github.com/pioarduino/platform-espressif32/releases/download/{release}/platform-espressif32.zip"
return f"https://github.com/pioarduino/platform-espressif32/releases/download/{release}/platform-espressif32.zip"
# if platform version is a valid version constraint, prefix the default package
cv.platformio_version_constraint(value)
return f"platformio/espressif32@{value}"
except cv.Invalid: except cv.Invalid:
return value return value
def _platform_is_platformio(value):
try:
ver = cv.Version.parse(cv.version_number(value))
return ver.major < 50
except cv.Invalid:
return "platformio" in value
def _detect_variant(value): def _detect_variant(value):
board = value.get(CONF_BOARD) board = value.get(CONF_BOARD)
variant = value.get(CONF_VARIANT) variant = value.get(CONF_VARIANT)
@@ -808,6 +742,8 @@ async def to_code(config):
conf = config[CONF_FRAMEWORK] conf = config[CONF_FRAMEWORK]
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
if CONF_SOURCE in conf:
cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]])
if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_CUSTOM_MAC]: if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_CUSTOM_MAC]:
cg.add_define("USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC") cg.add_define("USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC")
@@ -850,8 +786,6 @@ async def to_code(config):
cg.add_build_flag("-Wno-nonnull-compare") cg.add_build_flag("-Wno-nonnull-compare")
cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]])
add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True) add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True)
add_idf_sdkconfig_option( add_idf_sdkconfig_option(
f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True

View File

@@ -1,5 +1,8 @@
from collections.abc import Callable, MutableMapping
from enum import Enum from enum import Enum
import logging
import re import re
from typing import Any
from esphome import automation from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
@@ -9,16 +12,19 @@ from esphome.const import (
CONF_ENABLE_ON_BOOT, CONF_ENABLE_ON_BOOT,
CONF_ESPHOME, CONF_ESPHOME,
CONF_ID, CONF_ID,
CONF_MAX_CONNECTIONS,
CONF_NAME, CONF_NAME,
CONF_NAME_ADD_MAC_SUFFIX, CONF_NAME_ADD_MAC_SUFFIX,
) )
from esphome.core import TimePeriod from esphome.core import CORE, TimePeriod
import esphome.final_validate as fv import esphome.final_validate as fv
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
CODEOWNERS = ["@jesserockz", "@Rapsssito", "@bdraco"] CODEOWNERS = ["@jesserockz", "@Rapsssito", "@bdraco"]
DOMAIN = "esp32_ble" DOMAIN = "esp32_ble"
_LOGGER = logging.getLogger(__name__)
class BTLoggers(Enum): class BTLoggers(Enum):
"""Bluetooth logger categories available in ESP-IDF. """Bluetooth logger categories available in ESP-IDF.
@@ -127,6 +133,28 @@ CONF_DISABLE_BT_LOGS = "disable_bt_logs"
CONF_CONNECTION_TIMEOUT = "connection_timeout" CONF_CONNECTION_TIMEOUT = "connection_timeout"
CONF_MAX_NOTIFICATIONS = "max_notifications" CONF_MAX_NOTIFICATIONS = "max_notifications"
# BLE connection limits
# ESP-IDF CONFIG_BT_ACL_CONNECTIONS has range 1-9, default 4
# Total instances: 10 (ADV + SCAN + connections)
# - ADV only: up to 9 connections
# - SCAN only: up to 9 connections
# - ADV + SCAN: up to 8 connections
DEFAULT_MAX_CONNECTIONS = 3
IDF_MAX_CONNECTIONS = 9
# Connection slot tracking keys
KEY_ESP32_BLE = "esp32_ble"
KEY_USED_CONNECTION_SLOTS = "used_connection_slots"
# Export for use by other components (bluetooth_proxy, etc.)
__all__ = [
"DEFAULT_MAX_CONNECTIONS",
"IDF_MAX_CONNECTIONS",
"KEY_ESP32_BLE",
"KEY_USED_CONNECTION_SLOTS",
"consume_connection_slots",
]
NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2] NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2]
esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble") esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble")
@@ -183,6 +211,9 @@ CONFIG_SCHEMA = cv.Schema(
cv.positive_int, cv.positive_int,
cv.Range(min=1, max=64), cv.Range(min=1, max=64),
), ),
cv.Optional(CONF_MAX_CONNECTIONS, default=DEFAULT_MAX_CONNECTIONS): cv.All(
cv.positive_int, cv.Range(min=1, max=IDF_MAX_CONNECTIONS)
),
} }
).extend(cv.COMPONENT_SCHEMA) ).extend(cv.COMPONENT_SCHEMA)
@@ -230,6 +261,56 @@ def validate_variant(_):
raise cv.Invalid(f"{variant} does not support Bluetooth") raise cv.Invalid(f"{variant} does not support Bluetooth")
def consume_connection_slots(
value: int, consumer: str
) -> Callable[[MutableMapping], MutableMapping]:
"""Reserve BLE connection slots for a component.
Args:
value: Number of connection slots to reserve
consumer: Name of the component consuming the slots
Returns:
A validator function that records the slot usage
"""
def _consume_connection_slots(config: MutableMapping) -> MutableMapping:
data: dict[str, Any] = CORE.data.setdefault(KEY_ESP32_BLE, {})
slots: list[str] = data.setdefault(KEY_USED_CONNECTION_SLOTS, [])
slots.extend([consumer] * value)
return config
return _consume_connection_slots
def validate_connection_slots(max_connections: int) -> None:
"""Validate that BLE connection slots don't exceed the configured maximum."""
ble_data = CORE.data.get(KEY_ESP32_BLE, {})
used_slots = ble_data.get(KEY_USED_CONNECTION_SLOTS, [])
num_used = len(used_slots)
if num_used <= max_connections:
return
slot_users = ", ".join(used_slots)
if num_used > IDF_MAX_CONNECTIONS:
raise cv.Invalid(
f"BLE components require {num_used} connection slots but maximum is {IDF_MAX_CONNECTIONS}. "
f"Reduce the number of BLE clients. Components: {slot_users}"
)
_LOGGER.warning(
"BLE components require %d connection slot(s) but only %d configured. "
"Please set 'max_connections: %d' in the 'esp32_ble' component. "
"Components: %s",
num_used,
max_connections,
num_used,
slot_users,
)
def final_validation(config): def final_validation(config):
validate_variant(config) validate_variant(config)
if (name := config.get(CONF_NAME)) is not None: if (name := config.get(CONF_NAME)) is not None:
@@ -245,6 +326,10 @@ def final_validation(config):
# Set GATT Client/Server sdkconfig options based on which components are loaded # Set GATT Client/Server sdkconfig options based on which components are loaded
full_config = fv.full_config.get() full_config = fv.full_config.get()
# Validate connection slots usage
max_connections = config.get(CONF_MAX_CONNECTIONS, DEFAULT_MAX_CONNECTIONS)
validate_connection_slots(max_connections)
# Check if BLE Server is needed # Check if BLE Server is needed
has_ble_server = "esp32_ble_server" in full_config has_ble_server = "esp32_ble_server" in full_config
add_idf_sdkconfig_option("CONFIG_BT_GATTS_ENABLE", has_ble_server) add_idf_sdkconfig_option("CONFIG_BT_GATTS_ENABLE", has_ble_server)
@@ -255,6 +340,26 @@ def final_validation(config):
) )
add_idf_sdkconfig_option("CONFIG_BT_GATTC_ENABLE", has_ble_client) add_idf_sdkconfig_option("CONFIG_BT_GATTC_ENABLE", has_ble_client)
# Handle max_connections: check for deprecated location in esp32_ble_tracker
max_connections = config.get(CONF_MAX_CONNECTIONS, DEFAULT_MAX_CONNECTIONS)
# Use value from tracker if esp32_ble doesn't have it explicitly set (backward compat)
if "esp32_ble_tracker" in full_config:
tracker_config = full_config["esp32_ble_tracker"]
if "max_connections" in tracker_config and CONF_MAX_CONNECTIONS not in config:
max_connections = tracker_config["max_connections"]
# Set CONFIG_BT_ACL_CONNECTIONS to the maximum connections needed + 1 for ADV/SCAN
# This is the Bluedroid host stack total instance limit (range 1-9, default 4)
# Total instances = ADV/SCAN (1) + connection slots (max_connections)
# Shared between client (tracker/ble_client) and server
add_idf_sdkconfig_option("CONFIG_BT_ACL_CONNECTIONS", max_connections + 1)
# Set controller-specific max connections for ESP32 (classic)
# CONFIG_BTDM_CTRL_BLE_MAX_CONN is ESP32-specific controller limit (just connections, not ADV/SCAN)
# For newer chips (C3/S3/etc), different configs are used automatically
add_idf_sdkconfig_option("CONFIG_BTDM_CTRL_BLE_MAX_CONN", max_connections)
return config return config
@@ -270,6 +375,10 @@ async def to_code(config):
cg.add(var.set_name(name)) cg.add(var.set_name(name))
await cg.register_component(var, config) await cg.register_component(var, config)
# Define max connections for use in C++ code (e.g., ble_server.h)
max_connections = config.get(CONF_MAX_CONNECTIONS, DEFAULT_MAX_CONNECTIONS)
cg.add_define("USE_ESP32_BLE_MAX_CONNECTIONS", max_connections)
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True) add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True)

View File

@@ -213,15 +213,17 @@ bool ESP32BLE::ble_setup_() {
if (this->name_.has_value()) { if (this->name_.has_value()) {
name = this->name_.value(); name = this->name_.value();
if (App.is_name_add_mac_suffix_enabled()) { if (App.is_name_add_mac_suffix_enabled()) {
name += "-" + get_mac_address().substr(6); name += "-";
name += get_mac_address().substr(6);
} }
} else { } else {
name = App.get_name(); name = App.get_name();
if (name.length() > 20) { if (name.length() > 20) {
if (App.is_name_add_mac_suffix_enabled()) { if (App.is_name_add_mac_suffix_enabled()) {
name.erase(name.begin() + 13, name.end() - 7); // Remove characters between 13 and the mac address // Keep first 13 chars and last 7 chars (MAC suffix), remove middle
name.erase(13, name.length() - 20);
} else { } else {
name = name.substr(0, 20); name.resize(20);
} }
} }
} }

View File

@@ -152,7 +152,7 @@ void BLEAdvertising::loop() {
if (now - this->last_advertisement_time_ > this->advertising_cycle_time_) { if (now - this->last_advertisement_time_ > this->advertising_cycle_time_) {
this->stop(); this->stop();
this->current_adv_index_ += 1; this->current_adv_index_ += 1;
if (this->current_adv_index_ >= this->raw_advertisements_callbacks_.size()) { if (static_cast<size_t>(this->current_adv_index_) >= this->raw_advertisements_callbacks_.size()) {
this->current_adv_index_ = -1; this->current_adv_index_ = -1;
} }
this->start(); this->start();

View File

@@ -26,7 +26,7 @@ from esphome.const import (
from esphome.core import CORE from esphome.core import CORE
from esphome.schema_extractors import SCHEMA_EXTRACT from esphome.schema_extractors import SCHEMA_EXTRACT
AUTO_LOAD = ["esp32_ble", "bytebuffer", "event_emitter"] AUTO_LOAD = ["esp32_ble", "bytebuffer"]
CODEOWNERS = ["@jesserockz", "@clydebarrow", "@Rapsssito"] CODEOWNERS = ["@jesserockz", "@clydebarrow", "@Rapsssito"]
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
DOMAIN = "esp32_ble_server" DOMAIN = "esp32_ble_server"

View File

@@ -49,7 +49,11 @@ void BLECharacteristic::notify() {
this->service_->get_server()->get_connected_client_count() == 0) this->service_->get_server()->get_connected_client_count() == 0)
return; return;
for (auto &client : this->service_->get_server()->get_clients()) { const uint16_t *clients = this->service_->get_server()->get_clients();
uint8_t client_count = this->service_->get_server()->get_client_count();
for (uint8_t i = 0; i < client_count; i++) {
uint16_t client = clients[i];
size_t length = this->value_.size(); size_t length = this->value_.size();
// Find the client in the list of clients to notify // Find the client in the list of clients to notify
auto *entry = this->find_client_in_notify_list_(client); auto *entry = this->find_client_in_notify_list_(client);
@@ -73,7 +77,7 @@ void BLECharacteristic::notify() {
void BLECharacteristic::add_descriptor(BLEDescriptor *descriptor) { void BLECharacteristic::add_descriptor(BLEDescriptor *descriptor) {
// If the descriptor is the CCCD descriptor, listen to its write event to know if the client wants to be notified // If the descriptor is the CCCD descriptor, listen to its write event to know if the client wants to be notified
if (descriptor->get_uuid() == ESPBTUUID::from_uint16(ESP_GATT_UUID_CHAR_CLIENT_CONFIG)) { if (descriptor->get_uuid() == ESPBTUUID::from_uint16(ESP_GATT_UUID_CHAR_CLIENT_CONFIG)) {
descriptor->on(BLEDescriptorEvt::VectorEvt::ON_WRITE, [this](const std::vector<uint8_t> &value, uint16_t conn_id) { descriptor->on_write([this](std::span<const uint8_t> value, uint16_t conn_id) {
if (value.size() != 2) if (value.size() != 2)
return; return;
uint16_t cccd = encode_uint16(value[1], value[0]); uint16_t cccd = encode_uint16(value[1], value[0]);
@@ -121,69 +125,49 @@ bool BLECharacteristic::is_created() {
if (this->state_ != CREATING_DEPENDENTS) if (this->state_ != CREATING_DEPENDENTS)
return false; return false;
bool created = true;
for (auto *descriptor : this->descriptors_) { for (auto *descriptor : this->descriptors_) {
created &= descriptor->is_created(); if (!descriptor->is_created())
return false;
} }
if (created) // All descriptors are created if we reach here
this->state_ = CREATED; this->state_ = CREATED;
return this->state_ == CREATED; return true;
} }
bool BLECharacteristic::is_failed() { bool BLECharacteristic::is_failed() {
if (this->state_ == FAILED) if (this->state_ == FAILED)
return true; return true;
bool failed = false;
for (auto *descriptor : this->descriptors_) { for (auto *descriptor : this->descriptors_) {
failed |= descriptor->is_failed(); if (descriptor->is_failed()) {
this->state_ = FAILED;
return true;
}
}
return false;
}
void BLECharacteristic::set_property_bit_(esp_gatt_char_prop_t bit, bool value) {
if (value) {
this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | bit);
} else {
this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~bit);
} }
if (failed)
this->state_ = FAILED;
return this->state_ == FAILED;
} }
void BLECharacteristic::set_broadcast_property(bool value) { void BLECharacteristic::set_broadcast_property(bool value) {
if (value) { this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_BROADCAST, value);
this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST);
} else {
this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST);
}
} }
void BLECharacteristic::set_indicate_property(bool value) { void BLECharacteristic::set_indicate_property(bool value) {
if (value) { this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_INDICATE, value);
this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE);
} else {
this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE);
}
} }
void BLECharacteristic::set_notify_property(bool value) { void BLECharacteristic::set_notify_property(bool value) {
if (value) { this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_NOTIFY, value);
this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY);
} else {
this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY);
}
}
void BLECharacteristic::set_read_property(bool value) {
if (value) {
this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ);
} else {
this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ);
}
}
void BLECharacteristic::set_write_property(bool value) {
if (value) {
this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE);
} else {
this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE);
}
} }
void BLECharacteristic::set_read_property(bool value) { this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_READ, value); }
void BLECharacteristic::set_write_property(bool value) { this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_WRITE, value); }
void BLECharacteristic::set_write_no_response_property(bool value) { void BLECharacteristic::set_write_no_response_property(bool value) {
if (value) { this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_WRITE_NR, value);
this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR);
} else {
this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR);
}
} }
void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
@@ -208,8 +192,9 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt
if (!param->read.need_rsp) if (!param->read.need_rsp)
break; // For some reason you can request a read but not want a response break; // For some reason you can request a read but not want a response
this->EventEmitter<BLECharacteristicEvt::EmptyEvt, uint16_t>::emit_(BLECharacteristicEvt::EmptyEvt::ON_READ, if (this->on_read_callback_) {
param->read.conn_id); (*this->on_read_callback_)(param->read.conn_id);
}
uint16_t max_offset = 22; uint16_t max_offset = 22;
@@ -277,8 +262,9 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt
} }
if (!param->write.is_prep) { if (!param->write.is_prep) {
this->EventEmitter<BLECharacteristicEvt::VectorEvt, std::vector<uint8_t>, uint16_t>::emit_( if (this->on_write_callback_) {
BLECharacteristicEvt::VectorEvt::ON_WRITE, this->value_, param->write.conn_id); (*this->on_write_callback_)(this->value_, param->write.conn_id);
}
} }
break; break;
@@ -289,8 +275,9 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt
break; break;
this->write_event_ = false; this->write_event_ = false;
if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) { if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) {
this->EventEmitter<BLECharacteristicEvt::VectorEvt, std::vector<uint8_t>, uint16_t>::emit_( if (this->on_write_callback_) {
BLECharacteristicEvt::VectorEvt::ON_WRITE, this->value_, param->exec_write.conn_id); (*this->on_write_callback_)(this->value_, param->exec_write.conn_id);
}
} }
esp_err_t err = esp_err_t err =
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, nullptr); esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, nullptr);

View File

@@ -2,10 +2,12 @@
#include "ble_descriptor.h" #include "ble_descriptor.h"
#include "esphome/components/esp32_ble/ble_uuid.h" #include "esphome/components/esp32_ble/ble_uuid.h"
#include "esphome/components/event_emitter/event_emitter.h"
#include "esphome/components/bytebuffer/bytebuffer.h" #include "esphome/components/bytebuffer/bytebuffer.h"
#include <vector> #include <vector>
#include <span>
#include <functional>
#include <memory>
#ifdef USE_ESP32 #ifdef USE_ESP32
@@ -22,22 +24,10 @@ namespace esp32_ble_server {
using namespace esp32_ble; using namespace esp32_ble;
using namespace bytebuffer; using namespace bytebuffer;
using namespace event_emitter;
class BLEService; class BLEService;
namespace BLECharacteristicEvt { class BLECharacteristic {
enum VectorEvt {
ON_WRITE,
};
enum EmptyEvt {
ON_READ,
};
} // namespace BLECharacteristicEvt
class BLECharacteristic : public EventEmitter<BLECharacteristicEvt::VectorEvt, std::vector<uint8_t>, uint16_t>,
public EventEmitter<BLECharacteristicEvt::EmptyEvt, uint16_t> {
public: public:
BLECharacteristic(ESPBTUUID uuid, uint32_t properties); BLECharacteristic(ESPBTUUID uuid, uint32_t properties);
~BLECharacteristic(); ~BLECharacteristic();
@@ -76,6 +66,15 @@ class BLECharacteristic : public EventEmitter<BLECharacteristicEvt::VectorEvt, s
bool is_created(); bool is_created();
bool is_failed(); bool is_failed();
// Direct callback registration - only allocates when callback is set
void on_write(std::function<void(std::span<const uint8_t>, uint16_t)> &&callback) {
this->on_write_callback_ =
std::make_unique<std::function<void(std::span<const uint8_t>, uint16_t)>>(std::move(callback));
}
void on_read(std::function<void(uint16_t)> &&callback) {
this->on_read_callback_ = std::make_unique<std::function<void(uint16_t)>>(std::move(callback));
}
protected: protected:
bool write_event_{false}; bool write_event_{false};
BLEService *service_{}; BLEService *service_{};
@@ -98,6 +97,11 @@ class BLECharacteristic : public EventEmitter<BLECharacteristicEvt::VectorEvt, s
void remove_client_from_notify_list_(uint16_t conn_id); void remove_client_from_notify_list_(uint16_t conn_id);
ClientNotificationEntry *find_client_in_notify_list_(uint16_t conn_id); ClientNotificationEntry *find_client_in_notify_list_(uint16_t conn_id);
void set_property_bit_(esp_gatt_char_prop_t bit, bool value);
std::unique_ptr<std::function<void(std::span<const uint8_t>, uint16_t)>> on_write_callback_;
std::unique_ptr<std::function<void(uint16_t)>> on_read_callback_;
esp_gatt_perm_t permissions_ = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; esp_gatt_perm_t permissions_ = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE;
enum State : uint8_t { enum State : uint8_t {

View File

@@ -74,9 +74,10 @@ void BLEDescriptor::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
break; break;
this->value_.attr_len = param->write.len; this->value_.attr_len = param->write.len;
memcpy(this->value_.attr_value, param->write.value, param->write.len); memcpy(this->value_.attr_value, param->write.value, param->write.len);
this->emit_(BLEDescriptorEvt::VectorEvt::ON_WRITE, if (this->on_write_callback_) {
std::vector<uint8_t>(param->write.value, param->write.value + param->write.len), (*this->on_write_callback_)(std::span<const uint8_t>(param->write.value, param->write.len),
param->write.conn_id); param->write.conn_id);
}
break; break;
} }
default: default:

View File

@@ -1,30 +1,26 @@
#pragma once #pragma once
#include "esphome/components/esp32_ble/ble_uuid.h" #include "esphome/components/esp32_ble/ble_uuid.h"
#include "esphome/components/event_emitter/event_emitter.h"
#include "esphome/components/bytebuffer/bytebuffer.h" #include "esphome/components/bytebuffer/bytebuffer.h"
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <esp_gatt_defs.h> #include <esp_gatt_defs.h>
#include <esp_gatts_api.h> #include <esp_gatts_api.h>
#include <span>
#include <functional>
#include <memory>
namespace esphome { namespace esphome {
namespace esp32_ble_server { namespace esp32_ble_server {
using namespace esp32_ble; using namespace esp32_ble;
using namespace bytebuffer; using namespace bytebuffer;
using namespace event_emitter;
class BLECharacteristic; class BLECharacteristic;
namespace BLEDescriptorEvt { // Base class for BLE descriptors
enum VectorEvt { class BLEDescriptor {
ON_WRITE,
};
} // namespace BLEDescriptorEvt
class BLEDescriptor : public EventEmitter<BLEDescriptorEvt::VectorEvt, std::vector<uint8_t>, uint16_t> {
public: public:
BLEDescriptor(ESPBTUUID uuid, uint16_t max_len = 100, bool read = true, bool write = true); BLEDescriptor(ESPBTUUID uuid, uint16_t max_len = 100, bool read = true, bool write = true);
virtual ~BLEDescriptor(); virtual ~BLEDescriptor();
@@ -39,6 +35,12 @@ class BLEDescriptor : public EventEmitter<BLEDescriptorEvt::VectorEvt, std::vect
bool is_created() { return this->state_ == CREATED; } bool is_created() { return this->state_ == CREATED; }
bool is_failed() { return this->state_ == FAILED; } bool is_failed() { return this->state_ == FAILED; }
// Direct callback registration - only allocates when callback is set
void on_write(std::function<void(std::span<const uint8_t>, uint16_t)> &&callback) {
this->on_write_callback_ =
std::make_unique<std::function<void(std::span<const uint8_t>, uint16_t)>>(std::move(callback));
}
protected: protected:
BLECharacteristic *characteristic_{nullptr}; BLECharacteristic *characteristic_{nullptr};
ESPBTUUID uuid_; ESPBTUUID uuid_;
@@ -46,6 +48,8 @@ class BLEDescriptor : public EventEmitter<BLEDescriptorEvt::VectorEvt, std::vect
esp_attr_value_t value_{}; esp_attr_value_t value_{};
std::unique_ptr<std::function<void(std::span<const uint8_t>, uint16_t)>> on_write_callback_;
esp_gatt_perm_t permissions_{}; esp_gatt_perm_t permissions_{};
enum State : uint8_t { enum State : uint8_t {

View File

@@ -147,20 +147,28 @@ BLEService *BLEServer::get_service(ESPBTUUID uuid, uint8_t inst_id) {
return nullptr; return nullptr;
} }
void BLEServer::dispatch_callbacks_(CallbackType type, uint16_t conn_id) {
for (auto &entry : this->callbacks_) {
if (entry.type == type) {
entry.callback(conn_id);
}
}
}
void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param) { esp_ble_gatts_cb_param_t *param) {
switch (event) { switch (event) {
case ESP_GATTS_CONNECT_EVT: { case ESP_GATTS_CONNECT_EVT: {
ESP_LOGD(TAG, "BLE Client connected"); ESP_LOGD(TAG, "BLE Client connected");
this->add_client_(param->connect.conn_id); this->add_client_(param->connect.conn_id);
this->emit_(BLEServerEvt::EmptyEvt::ON_CONNECT, param->connect.conn_id); this->dispatch_callbacks_(CallbackType::ON_CONNECT, param->connect.conn_id);
break; break;
} }
case ESP_GATTS_DISCONNECT_EVT: { case ESP_GATTS_DISCONNECT_EVT: {
ESP_LOGD(TAG, "BLE Client disconnected"); ESP_LOGD(TAG, "BLE Client disconnected");
this->remove_client_(param->disconnect.conn_id); this->remove_client_(param->disconnect.conn_id);
this->parent_->advertising_start(); this->parent_->advertising_start();
this->emit_(BLEServerEvt::EmptyEvt::ON_DISCONNECT, param->disconnect.conn_id); this->dispatch_callbacks_(CallbackType::ON_DISCONNECT, param->disconnect.conn_id);
break; break;
} }
case ESP_GATTS_REG_EVT: { case ESP_GATTS_REG_EVT: {
@@ -177,9 +185,38 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga
} }
} }
int8_t BLEServer::find_client_index_(uint16_t conn_id) const {
for (uint8_t i = 0; i < this->client_count_; i++) {
if (this->clients_[i] == conn_id)
return i;
}
return -1;
}
void BLEServer::add_client_(uint16_t conn_id) {
// Check if already in list
if (this->find_client_index_(conn_id) >= 0)
return;
// Add if there's space
if (this->client_count_ < USE_ESP32_BLE_MAX_CONNECTIONS) {
this->clients_[this->client_count_++] = conn_id;
} else {
// This should never happen since max clients is known at compile time
ESP_LOGE(TAG, "Client array full");
}
}
void BLEServer::remove_client_(uint16_t conn_id) {
int8_t index = this->find_client_index_(conn_id);
if (index >= 0) {
// Replace with last element and decrement count (client order not preserved)
this->clients_[index] = this->clients_[--this->client_count_];
}
}
void BLEServer::ble_before_disabled_event_handler() { void BLEServer::ble_before_disabled_event_handler() {
// Delete all clients // Delete all clients
this->clients_.clear(); this->client_count_ = 0;
// Delete all services // Delete all services
for (auto &entry : this->services_) { for (auto &entry : this->services_) {
entry.service->do_delete(); entry.service->do_delete();

View File

@@ -12,7 +12,7 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <functional>
#ifdef USE_ESP32 #ifdef USE_ESP32
@@ -24,18 +24,7 @@ namespace esp32_ble_server {
using namespace esp32_ble; using namespace esp32_ble;
using namespace bytebuffer; using namespace bytebuffer;
namespace BLEServerEvt { class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEventHandler, public Parented<ESP32BLE> {
enum EmptyEvt {
ON_CONNECT,
ON_DISCONNECT,
};
} // namespace BLEServerEvt
class BLEServer : public Component,
public GATTsEventHandler,
public BLEStatusEventHandler,
public Parented<ESP32BLE>,
public EventEmitter<BLEServerEvt::EmptyEvt, uint16_t> {
public: public:
void setup() override; void setup() override;
void loop() override; void loop() override;
@@ -57,15 +46,34 @@ class BLEServer : public Component,
void set_device_information_service(BLEService *service) { this->device_information_service_ = service; } void set_device_information_service(BLEService *service) { this->device_information_service_ = service; }
esp_gatt_if_t get_gatts_if() { return this->gatts_if_; } esp_gatt_if_t get_gatts_if() { return this->gatts_if_; }
uint32_t get_connected_client_count() { return this->clients_.size(); } uint32_t get_connected_client_count() { return this->client_count_; }
const std::unordered_set<uint16_t> &get_clients() { return this->clients_; } const uint16_t *get_clients() const { return this->clients_; }
uint8_t get_client_count() const { return this->client_count_; }
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param) override; esp_ble_gatts_cb_param_t *param) override;
void ble_before_disabled_event_handler() override; void ble_before_disabled_event_handler() override;
// Direct callback registration - supports multiple callbacks
void on_connect(std::function<void(uint16_t)> &&callback) {
this->callbacks_.push_back({CallbackType::ON_CONNECT, std::move(callback)});
}
void on_disconnect(std::function<void(uint16_t)> &&callback) {
this->callbacks_.push_back({CallbackType::ON_DISCONNECT, std::move(callback)});
}
protected: protected:
enum class CallbackType : uint8_t {
ON_CONNECT,
ON_DISCONNECT,
};
struct CallbackEntry {
CallbackType type;
std::function<void(uint16_t)> callback;
};
struct ServiceEntry { struct ServiceEntry {
ESPBTUUID uuid; ESPBTUUID uuid;
uint8_t inst_id; uint8_t inst_id;
@@ -74,14 +82,19 @@ class BLEServer : public Component,
void restart_advertising_(); void restart_advertising_();
void add_client_(uint16_t conn_id) { this->clients_.insert(conn_id); } int8_t find_client_index_(uint16_t conn_id) const;
void remove_client_(uint16_t conn_id) { this->clients_.erase(conn_id); } void add_client_(uint16_t conn_id);
void remove_client_(uint16_t conn_id);
void dispatch_callbacks_(CallbackType type, uint16_t conn_id);
std::vector<CallbackEntry> callbacks_;
std::vector<uint8_t> manufacturer_data_{}; std::vector<uint8_t> manufacturer_data_{};
esp_gatt_if_t gatts_if_{0}; esp_gatt_if_t gatts_if_{0};
bool registered_{false}; bool registered_{false};
std::unordered_set<uint16_t> clients_; uint16_t clients_[USE_ESP32_BLE_MAX_CONNECTIONS]{};
uint8_t client_count_{0};
std::vector<ServiceEntry> services_{}; std::vector<ServiceEntry> services_{};
std::vector<BLEService *> services_to_start_{}; std::vector<BLEService *> services_to_start_{};
BLEService *device_information_service_{}; BLEService *device_information_service_{};

View File

@@ -14,9 +14,10 @@ Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_characteristic_on_w
BLECharacteristic *characteristic) { BLECharacteristic *characteristic) {
Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger = // NOLINT(cppcoreguidelines-owning-memory) Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger = // NOLINT(cppcoreguidelines-owning-memory)
new Trigger<std::vector<uint8_t>, uint16_t>(); new Trigger<std::vector<uint8_t>, uint16_t>();
characteristic->EventEmitter<BLECharacteristicEvt::VectorEvt, std::vector<uint8_t>, uint16_t>::on( characteristic->on_write([on_write_trigger](std::span<const uint8_t> data, uint16_t id) {
BLECharacteristicEvt::VectorEvt::ON_WRITE, // Convert span to vector for trigger
[on_write_trigger](const std::vector<uint8_t> &data, uint16_t id) { on_write_trigger->trigger(data, id); }); on_write_trigger->trigger(std::vector<uint8_t>(data.begin(), data.end()), id);
});
return on_write_trigger; return on_write_trigger;
} }
#endif #endif
@@ -25,9 +26,10 @@ Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_characteristic_on_w
Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_descriptor_on_write_trigger(BLEDescriptor *descriptor) { Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_descriptor_on_write_trigger(BLEDescriptor *descriptor) {
Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger = // NOLINT(cppcoreguidelines-owning-memory) Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger = // NOLINT(cppcoreguidelines-owning-memory)
new Trigger<std::vector<uint8_t>, uint16_t>(); new Trigger<std::vector<uint8_t>, uint16_t>();
descriptor->on( descriptor->on_write([on_write_trigger](std::span<const uint8_t> data, uint16_t id) {
BLEDescriptorEvt::VectorEvt::ON_WRITE, // Convert span to vector for trigger
[on_write_trigger](const std::vector<uint8_t> &data, uint16_t id) { on_write_trigger->trigger(data, id); }); on_write_trigger->trigger(std::vector<uint8_t>(data.begin(), data.end()), id);
});
return on_write_trigger; return on_write_trigger;
} }
#endif #endif
@@ -35,8 +37,7 @@ Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_descriptor_on_write
#ifdef USE_ESP32_BLE_SERVER_ON_CONNECT #ifdef USE_ESP32_BLE_SERVER_ON_CONNECT
Trigger<uint16_t> *BLETriggers::create_server_on_connect_trigger(BLEServer *server) { Trigger<uint16_t> *BLETriggers::create_server_on_connect_trigger(BLEServer *server) {
Trigger<uint16_t> *on_connect_trigger = new Trigger<uint16_t>(); // NOLINT(cppcoreguidelines-owning-memory) Trigger<uint16_t> *on_connect_trigger = new Trigger<uint16_t>(); // NOLINT(cppcoreguidelines-owning-memory)
server->on(BLEServerEvt::EmptyEvt::ON_CONNECT, server->on_connect([on_connect_trigger](uint16_t conn_id) { on_connect_trigger->trigger(conn_id); });
[on_connect_trigger](uint16_t conn_id) { on_connect_trigger->trigger(conn_id); });
return on_connect_trigger; return on_connect_trigger;
} }
#endif #endif
@@ -44,38 +45,22 @@ Trigger<uint16_t> *BLETriggers::create_server_on_connect_trigger(BLEServer *serv
#ifdef USE_ESP32_BLE_SERVER_ON_DISCONNECT #ifdef USE_ESP32_BLE_SERVER_ON_DISCONNECT
Trigger<uint16_t> *BLETriggers::create_server_on_disconnect_trigger(BLEServer *server) { Trigger<uint16_t> *BLETriggers::create_server_on_disconnect_trigger(BLEServer *server) {
Trigger<uint16_t> *on_disconnect_trigger = new Trigger<uint16_t>(); // NOLINT(cppcoreguidelines-owning-memory) Trigger<uint16_t> *on_disconnect_trigger = new Trigger<uint16_t>(); // NOLINT(cppcoreguidelines-owning-memory)
server->on(BLEServerEvt::EmptyEvt::ON_DISCONNECT, server->on_disconnect([on_disconnect_trigger](uint16_t conn_id) { on_disconnect_trigger->trigger(conn_id); });
[on_disconnect_trigger](uint16_t conn_id) { on_disconnect_trigger->trigger(conn_id); });
return on_disconnect_trigger; return on_disconnect_trigger;
} }
#endif #endif
#ifdef USE_ESP32_BLE_SERVER_SET_VALUE_ACTION #ifdef USE_ESP32_BLE_SERVER_SET_VALUE_ACTION
void BLECharacteristicSetValueActionManager::set_listener(BLECharacteristic *characteristic, void BLECharacteristicSetValueActionManager::set_listener(BLECharacteristic *characteristic,
EventEmitterListenerID listener_id,
const std::function<void()> &pre_notify_listener) { const std::function<void()> &pre_notify_listener) {
// Find and remove existing listener for this characteristic // Find and remove existing listener for this characteristic
auto *existing = this->find_listener_(characteristic); auto *existing = this->find_listener_(characteristic);
if (existing != nullptr) { if (existing != nullptr) {
// Remove the previous listener
characteristic->EventEmitter<BLECharacteristicEvt::EmptyEvt, uint16_t>::off(BLECharacteristicEvt::EmptyEvt::ON_READ,
existing->listener_id);
// Remove the pre-notify listener
this->off(BLECharacteristicSetValueActionEvt::PRE_NOTIFY, existing->pre_notify_listener_id);
// Remove from vector // Remove from vector
this->remove_listener_(characteristic); this->remove_listener_(characteristic);
} }
// Create a new listener for the pre-notify event
EventEmitterListenerID pre_notify_listener_id =
this->on(BLECharacteristicSetValueActionEvt::PRE_NOTIFY,
[pre_notify_listener, characteristic](const BLECharacteristic *evt_characteristic) {
// Only call the pre-notify listener if the characteristic is the one we are interested in
if (characteristic == evt_characteristic) {
pre_notify_listener();
}
});
// Save the entry to the vector // Save the entry to the vector
this->listeners_.push_back({characteristic, listener_id, pre_notify_listener_id}); this->listeners_.push_back({characteristic, pre_notify_listener});
} }
BLECharacteristicSetValueActionManager::ListenerEntry *BLECharacteristicSetValueActionManager::find_listener_( BLECharacteristicSetValueActionManager::ListenerEntry *BLECharacteristicSetValueActionManager::find_listener_(

View File

@@ -4,7 +4,6 @@
#include "ble_characteristic.h" #include "ble_characteristic.h"
#include "ble_descriptor.h" #include "ble_descriptor.h"
#include "esphome/components/event_emitter/event_emitter.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include <vector> #include <vector>
@@ -18,10 +17,6 @@ namespace esp32_ble_server {
namespace esp32_ble_server_automations { namespace esp32_ble_server_automations {
using namespace esp32_ble; using namespace esp32_ble;
using namespace event_emitter;
// Invalid listener ID constant - 0 is used as sentinel value in EventEmitter
static constexpr EventEmitterListenerID INVALID_LISTENER_ID = 0;
class BLETriggers { class BLETriggers {
public: public:
@@ -41,38 +36,29 @@ class BLETriggers {
}; };
#ifdef USE_ESP32_BLE_SERVER_SET_VALUE_ACTION #ifdef USE_ESP32_BLE_SERVER_SET_VALUE_ACTION
enum BLECharacteristicSetValueActionEvt {
PRE_NOTIFY,
};
// Class to make sure only one BLECharacteristicSetValueAction is active at a time for each characteristic // Class to make sure only one BLECharacteristicSetValueAction is active at a time for each characteristic
class BLECharacteristicSetValueActionManager class BLECharacteristicSetValueActionManager {
: public EventEmitter<BLECharacteristicSetValueActionEvt, BLECharacteristic *> {
public: public:
// Singleton pattern // Singleton pattern
static BLECharacteristicSetValueActionManager *get_instance() { static BLECharacteristicSetValueActionManager *get_instance() {
static BLECharacteristicSetValueActionManager instance; static BLECharacteristicSetValueActionManager instance;
return &instance; return &instance;
} }
void set_listener(BLECharacteristic *characteristic, EventEmitterListenerID listener_id, void set_listener(BLECharacteristic *characteristic, const std::function<void()> &pre_notify_listener);
const std::function<void()> &pre_notify_listener); bool has_listener(BLECharacteristic *characteristic) { return this->find_listener_(characteristic) != nullptr; }
EventEmitterListenerID get_listener(BLECharacteristic *characteristic) { void emit_pre_notify(BLECharacteristic *characteristic) {
for (const auto &entry : this->listeners_) { for (const auto &entry : this->listeners_) {
if (entry.characteristic == characteristic) { if (entry.characteristic == characteristic) {
return entry.listener_id; entry.pre_notify_listener();
break;
} }
} }
return INVALID_LISTENER_ID;
}
void emit_pre_notify(BLECharacteristic *characteristic) {
this->emit_(BLECharacteristicSetValueActionEvt::PRE_NOTIFY, characteristic);
} }
private: private:
struct ListenerEntry { struct ListenerEntry {
BLECharacteristic *characteristic; BLECharacteristic *characteristic;
EventEmitterListenerID listener_id; std::function<void()> pre_notify_listener;
EventEmitterListenerID pre_notify_listener_id;
}; };
std::vector<ListenerEntry> listeners_; std::vector<ListenerEntry> listeners_;
@@ -87,24 +73,22 @@ template<typename... Ts> class BLECharacteristicSetValueAction : public Action<T
void set_buffer(ByteBuffer buffer) { this->set_buffer(buffer.get_data()); } void set_buffer(ByteBuffer buffer) { this->set_buffer(buffer.get_data()); }
void play(Ts... x) override { void play(Ts... x) override {
// If the listener is already set, do nothing // If the listener is already set, do nothing
if (BLECharacteristicSetValueActionManager::get_instance()->get_listener(this->parent_) == this->listener_id_) if (BLECharacteristicSetValueActionManager::get_instance()->has_listener(this->parent_))
return; return;
// Set initial value // Set initial value
this->parent_->set_value(this->buffer_.value(x...)); this->parent_->set_value(this->buffer_.value(x...));
// Set the listener for read events // Set the listener for read events
this->listener_id_ = this->parent_->EventEmitter<BLECharacteristicEvt::EmptyEvt, uint16_t>::on( this->parent_->on_read([this, x...](uint16_t id) {
BLECharacteristicEvt::EmptyEvt::ON_READ, [this, x...](uint16_t id) { // Set the value of the characteristic every time it is read
// Set the value of the characteristic every time it is read this->parent_->set_value(this->buffer_.value(x...));
this->parent_->set_value(this->buffer_.value(x...)); });
});
// Set the listener in the global manager so only one BLECharacteristicSetValueAction is set for each characteristic // Set the listener in the global manager so only one BLECharacteristicSetValueAction is set for each characteristic
BLECharacteristicSetValueActionManager::get_instance()->set_listener( BLECharacteristicSetValueActionManager::get_instance()->set_listener(
this->parent_, this->listener_id_, [this, x...]() { this->parent_->set_value(this->buffer_.value(x...)); }); this->parent_, [this, x...]() { this->parent_->set_value(this->buffer_.value(x...)); });
} }
protected: protected:
BLECharacteristic *parent_; BLECharacteristic *parent_;
EventEmitterListenerID listener_id_;
}; };
#endif // USE_ESP32_BLE_SERVER_SET_VALUE_ACTION #endif // USE_ESP32_BLE_SERVER_SET_VALUE_ACTION

View File

@@ -1,14 +1,13 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable, MutableMapping
import logging import logging
from typing import Any
from esphome import automation from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import esp32_ble from esphome.components import esp32_ble
from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.components.esp32 import add_idf_sdkconfig_option
from esphome.components.esp32_ble import ( from esphome.components.esp32_ble import (
IDF_MAX_CONNECTIONS,
BTLoggers, BTLoggers,
bt_uuid, bt_uuid,
bt_uuid16_format, bt_uuid16_format,
@@ -24,6 +23,7 @@ from esphome.const import (
CONF_INTERVAL, CONF_INTERVAL,
CONF_MAC_ADDRESS, CONF_MAC_ADDRESS,
CONF_MANUFACTURER_ID, CONF_MANUFACTURER_ID,
CONF_MAX_CONNECTIONS,
CONF_ON_BLE_ADVERTISE, CONF_ON_BLE_ADVERTISE,
CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE, CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE,
CONF_ON_BLE_SERVICE_DATA_ADVERTISE, CONF_ON_BLE_SERVICE_DATA_ADVERTISE,
@@ -38,19 +38,12 @@ AUTO_LOAD = ["esp32_ble"]
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
CODEOWNERS = ["@bdraco"] CODEOWNERS = ["@bdraco"]
KEY_ESP32_BLE_TRACKER = "esp32_ble_tracker"
KEY_USED_CONNECTION_SLOTS = "used_connection_slots"
CONF_MAX_CONNECTIONS = "max_connections"
CONF_ESP32_BLE_ID = "esp32_ble_id" CONF_ESP32_BLE_ID = "esp32_ble_id"
CONF_SCAN_PARAMETERS = "scan_parameters" CONF_SCAN_PARAMETERS = "scan_parameters"
CONF_WINDOW = "window" CONF_WINDOW = "window"
CONF_ON_SCAN_END = "on_scan_end" CONF_ON_SCAN_END = "on_scan_end"
CONF_SOFTWARE_COEXISTENCE = "software_coexistence" CONF_SOFTWARE_COEXISTENCE = "software_coexistence"
DEFAULT_MAX_CONNECTIONS = 3
IDF_MAX_CONNECTIONS = 9
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -128,6 +121,15 @@ def validate_scan_parameters(config):
return config return config
def validate_max_connections_deprecated(config: ConfigType) -> ConfigType:
if CONF_MAX_CONNECTIONS in config:
_LOGGER.warning(
"The 'max_connections' option in 'esp32_ble_tracker' is deprecated. "
"Please move it to the 'esp32_ble' component instead."
)
return config
def as_hex(value): def as_hex(value):
return cg.RawExpression(f"0x{value}ULL") return cg.RawExpression(f"0x{value}ULL")
@@ -150,24 +152,12 @@ def as_reversed_hex_array(value):
) )
def consume_connection_slots(
value: int, consumer: str
) -> Callable[[MutableMapping], MutableMapping]:
def _consume_connection_slots(config: MutableMapping) -> MutableMapping:
data: dict[str, Any] = CORE.data.setdefault(KEY_ESP32_BLE_TRACKER, {})
slots: list[str] = data.setdefault(KEY_USED_CONNECTION_SLOTS, [])
slots.extend([consumer] * value)
return config
return _consume_connection_slots
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(ESP32BLETracker), cv.GenerateID(): cv.declare_id(ESP32BLETracker),
cv.GenerateID(esp32_ble.CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE), cv.GenerateID(esp32_ble.CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE),
cv.Optional(CONF_MAX_CONNECTIONS, default=DEFAULT_MAX_CONNECTIONS): cv.All( cv.Optional(CONF_MAX_CONNECTIONS): cv.All(
cv.positive_int, cv.Range(min=0, max=IDF_MAX_CONNECTIONS) cv.positive_int, cv.Range(min=0, max=IDF_MAX_CONNECTIONS)
), ),
cv.Optional(CONF_SCAN_PARAMETERS, default={}): cv.All( cv.Optional(CONF_SCAN_PARAMETERS, default={}): cv.All(
@@ -224,48 +214,11 @@ CONFIG_SCHEMA = cv.All(
cv.OnlyWith(CONF_SOFTWARE_COEXISTENCE, "wifi", default=True): bool, cv.OnlyWith(CONF_SOFTWARE_COEXISTENCE, "wifi", default=True): bool,
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
validate_max_connections_deprecated,
) )
def validate_remaining_connections(config): FINAL_VALIDATE_SCHEMA = esp32_ble.validate_variant
data: dict[str, Any] = CORE.data.get(KEY_ESP32_BLE_TRACKER, {})
slots: list[str] = data.get(KEY_USED_CONNECTION_SLOTS, [])
used_slots = len(slots)
if used_slots <= config[CONF_MAX_CONNECTIONS]:
return config
slot_users = ", ".join(slots)
if used_slots < IDF_MAX_CONNECTIONS:
_LOGGER.warning(
"esp32_ble_tracker exceeded `%s`: components attempted to consume %d "
"connection slot(s) out of available configured maximum %d connection "
"slot(s); The system automatically increased `%s` to %d to match the "
"number of used connection slot(s) by components: %s.",
CONF_MAX_CONNECTIONS,
used_slots,
config[CONF_MAX_CONNECTIONS],
CONF_MAX_CONNECTIONS,
used_slots,
slot_users,
)
config[CONF_MAX_CONNECTIONS] = used_slots
return config
msg = (
f"esp32_ble_tracker exceeded `{CONF_MAX_CONNECTIONS}`: "
f"components attempted to consume {used_slots} connection slot(s) "
f"out of available configured maximum {config[CONF_MAX_CONNECTIONS]} "
f"connection slot(s); Decrease the number of BLE clients ({slot_users})"
)
if config[CONF_MAX_CONNECTIONS] < IDF_MAX_CONNECTIONS:
msg += f" or increase {CONF_MAX_CONNECTIONS}` to {used_slots}"
msg += f" to stay under the {IDF_MAX_CONNECTIONS} connection slot(s) limit."
raise cv.Invalid(msg)
FINAL_VALIDATE_SCHEMA = cv.All(
validate_remaining_connections, esp32_ble.validate_variant
)
ESP_BLE_DEVICE_SCHEMA = cv.Schema( ESP_BLE_DEVICE_SCHEMA = cv.Schema(
{ {
@@ -345,10 +298,8 @@ async def to_code(config):
# Match arduino CONFIG_BTU_TASK_STACK_SIZE # Match arduino CONFIG_BTU_TASK_STACK_SIZE
# https://github.com/espressif/arduino-esp32/blob/fd72cf46ad6fc1a6de99c1d83ba8eba17d80a4ee/tools/sdk/esp32/sdkconfig#L1866 # https://github.com/espressif/arduino-esp32/blob/fd72cf46ad6fc1a6de99c1d83ba8eba17d80a4ee/tools/sdk/esp32/sdkconfig#L1866
add_idf_sdkconfig_option("CONFIG_BT_BTU_TASK_STACK_SIZE", 8192) add_idf_sdkconfig_option("CONFIG_BT_BTU_TASK_STACK_SIZE", 8192)
add_idf_sdkconfig_option("CONFIG_BT_ACL_CONNECTIONS", 9) # Note: CONFIG_BT_ACL_CONNECTIONS and CONFIG_BTDM_CTRL_BLE_MAX_CONN are now
add_idf_sdkconfig_option( # configured in esp32_ble component based on max_connections setting
"CONFIG_BTDM_CTRL_BLE_MAX_CONN", config[CONF_MAX_CONNECTIONS]
)
cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts
cg.add_define("USE_ESP32_BLE_CLIENT") cg.add_define("USE_ESP32_BLE_CLIENT")

View File

@@ -67,8 +67,16 @@ static bool get_bitrate(canbus::CanSpeed bitrate, twai_timing_config_t *t_config
} }
bool ESP32Can::setup_internal() { bool ESP32Can::setup_internal() {
static int next_twai_ctrl_num = 0;
if (static_cast<unsigned>(next_twai_ctrl_num) >= SOC_TWAI_CONTROLLER_NUM) {
ESP_LOGW(TAG, "Maximum number of esp32_can components created already");
this->mark_failed();
return false;
}
twai_general_config_t g_config = twai_general_config_t g_config =
TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, TWAI_MODE_NORMAL); TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, TWAI_MODE_NORMAL);
g_config.controller_id = next_twai_ctrl_num++;
if (this->tx_queue_len_.has_value()) { if (this->tx_queue_len_.has_value()) {
g_config.tx_queue_len = this->tx_queue_len_.value(); g_config.tx_queue_len = this->tx_queue_len_.value();
} }
@@ -86,14 +94,14 @@ bool ESP32Can::setup_internal() {
} }
// Install TWAI driver // Install TWAI driver
if (twai_driver_install(&g_config, &t_config, &f_config) != ESP_OK) { if (twai_driver_install_v2(&g_config, &t_config, &f_config, &(this->twai_handle_)) != ESP_OK) {
// Failed to install driver // Failed to install driver
this->mark_failed(); this->mark_failed();
return false; return false;
} }
// Start TWAI driver // Start TWAI driver
if (twai_start() != ESP_OK) { if (twai_start_v2(this->twai_handle_) != ESP_OK) {
// Failed to start driver // Failed to start driver
this->mark_failed(); this->mark_failed();
return false; return false;
@@ -102,6 +110,11 @@ bool ESP32Can::setup_internal() {
} }
canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) { canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) {
if (this->twai_handle_ == nullptr) {
// not setup yet or setup failed
return canbus::ERROR_FAIL;
}
if (frame->can_data_length_code > canbus::CAN_MAX_DATA_LENGTH) { if (frame->can_data_length_code > canbus::CAN_MAX_DATA_LENGTH) {
return canbus::ERROR_FAILTX; return canbus::ERROR_FAILTX;
} }
@@ -124,7 +137,7 @@ canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) {
memcpy(message.data, frame->data, frame->can_data_length_code); memcpy(message.data, frame->data, frame->can_data_length_code);
} }
if (twai_transmit(&message, this->tx_enqueue_timeout_ticks_) == ESP_OK) { if (twai_transmit_v2(this->twai_handle_, &message, this->tx_enqueue_timeout_ticks_) == ESP_OK) {
return canbus::ERROR_OK; return canbus::ERROR_OK;
} else { } else {
return canbus::ERROR_ALLTXBUSY; return canbus::ERROR_ALLTXBUSY;
@@ -132,9 +145,14 @@ canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) {
} }
canbus::Error ESP32Can::read_message(struct canbus::CanFrame *frame) { canbus::Error ESP32Can::read_message(struct canbus::CanFrame *frame) {
if (this->twai_handle_ == nullptr) {
// not setup yet or setup failed
return canbus::ERROR_FAIL;
}
twai_message_t message; twai_message_t message;
if (twai_receive(&message, 0) != ESP_OK) { if (twai_receive_v2(this->twai_handle_, &message, 0) != ESP_OK) {
return canbus::ERROR_NOMSG; return canbus::ERROR_NOMSG;
} }

View File

@@ -5,6 +5,8 @@
#include "esphome/components/canbus/canbus.h" #include "esphome/components/canbus/canbus.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include <driver/twai.h>
namespace esphome { namespace esphome {
namespace esp32_can { namespace esp32_can {
@@ -29,6 +31,7 @@ class ESP32Can : public canbus::Canbus {
TickType_t tx_enqueue_timeout_ticks_{}; TickType_t tx_enqueue_timeout_ticks_{};
optional<uint32_t> tx_queue_len_{}; optional<uint32_t> tx_queue_len_{};
optional<uint32_t> rx_queue_len_{}; optional<uint32_t> rx_queue_len_{};
twai_handle_t twai_handle_{nullptr};
}; };
} // namespace esp32_can } // namespace esp32_can

View File

@@ -38,8 +38,7 @@ void ESP32ImprovComponent::setup() {
}); });
} }
#endif #endif
global_ble_server->on(BLEServerEvt::EmptyEvt::ON_DISCONNECT, global_ble_server->on_disconnect([this](uint16_t conn_id) { this->set_error_(improv::ERROR_NONE); });
[this](uint16_t conn_id) { this->set_error_(improv::ERROR_NONE); });
// Start with loop disabled - will be enabled by start() when needed // Start with loop disabled - will be enabled by start() when needed
this->disable_loop(); this->disable_loop();
@@ -57,12 +56,11 @@ void ESP32ImprovComponent::setup_characteristics() {
this->error_->add_descriptor(error_descriptor); this->error_->add_descriptor(error_descriptor);
this->rpc_ = this->service_->create_characteristic(improv::RPC_COMMAND_UUID, BLECharacteristic::PROPERTY_WRITE); this->rpc_ = this->service_->create_characteristic(improv::RPC_COMMAND_UUID, BLECharacteristic::PROPERTY_WRITE);
this->rpc_->EventEmitter<BLECharacteristicEvt::VectorEvt, std::vector<uint8_t>, uint16_t>::on( this->rpc_->on_write([this](std::span<const uint8_t> data, uint16_t id) {
BLECharacteristicEvt::VectorEvt::ON_WRITE, [this](const std::vector<uint8_t> &data, uint16_t id) { if (!data.empty()) {
if (!data.empty()) { this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end());
this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end()); }
} });
});
BLEDescriptor *rpc_descriptor = new BLE2902(); BLEDescriptor *rpc_descriptor = new BLE2902();
this->rpc_->add_descriptor(rpc_descriptor); this->rpc_->add_descriptor(rpc_descriptor);

View File

@@ -35,7 +35,7 @@ static size_t IRAM_ATTR HOT encoder_callback(const void *data, size_t size, size
if (symbols_free < RMT_SYMBOLS_PER_BYTE) { if (symbols_free < RMT_SYMBOLS_PER_BYTE) {
return 0; return 0;
} }
for (int32_t i = 0; i < RMT_SYMBOLS_PER_BYTE; i++) { for (size_t i = 0; i < RMT_SYMBOLS_PER_BYTE; i++) {
if (bytes[index] & (1 << (7 - i))) { if (bytes[index] & (1 << (7 - i))) {
symbols[i] = params->bit1; symbols[i] = params->bit1;
} else { } else {

View File

@@ -614,24 +614,67 @@ bool ESPHomeOTAComponent::handle_auth_send_() {
return false; return false;
} }
// Generate nonce with appropriate hasher // Generate nonce - hasher must be created and used in same stack frame
bool success = false; // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION REQUIREMENTS:
// 1. Hash objects must NEVER be passed to another function (different stack frame)
// 2. NO Variable Length Arrays (VLAs) - they corrupt the stack with hardware DMA
// 3. All hash operations (init/add/calculate) must happen in the SAME function where object is created
// Violating these causes truncated hash output (20 bytes instead of 32) or memory corruption.
//
// Buffer layout after AUTH_READ completes:
// [0]: auth_type (1 byte)
// [1...hex_size]: nonce (hex_size bytes) - our random nonce sent in AUTH_SEND
// [1+hex_size...1+2*hex_size-1]: cnonce (hex_size bytes) - client's nonce
// [1+2*hex_size...1+3*hex_size-1]: response (hex_size bytes) - client's hash
// Declare both hash objects in same stack frame, use pointer to select.
// NOTE: Both objects are declared here even though only one is used. This is REQUIRED for ESP32-S3
// hardware SHA acceleration - the object must exist in this stack frame for all operations.
// Do NOT try to "optimize" by creating the object inside the if block, as it would go out of scope.
#ifdef USE_OTA_SHA256
sha256::SHA256 sha_hasher;
#endif
#ifdef USE_OTA_MD5
md5::MD5Digest md5_hasher;
#endif
HashBase *hasher = nullptr;
#ifdef USE_OTA_SHA256 #ifdef USE_OTA_SHA256
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) { if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) {
sha256::SHA256 sha_hasher; hasher = &sha_hasher;
success = this->prepare_auth_nonce_(&sha_hasher);
} }
#endif #endif
#ifdef USE_OTA_MD5 #ifdef USE_OTA_MD5
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_AUTH) { if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_AUTH) {
md5::MD5Digest md5_hasher; hasher = &md5_hasher;
success = this->prepare_auth_nonce_(&md5_hasher);
} }
#endif #endif
if (!success) { const size_t hex_size = hasher->get_size() * 2;
const size_t nonce_len = hasher->get_size() / 4;
const size_t auth_buf_size = 1 + 3 * hex_size;
this->auth_buf_ = std::make_unique<uint8_t[]>(auth_buf_size);
this->auth_buf_pos_ = 0;
char *buf = reinterpret_cast<char *>(this->auth_buf_.get() + 1);
if (!random_bytes(reinterpret_cast<uint8_t *>(buf), nonce_len)) {
this->log_auth_warning_(LOG_STR("Random failed"));
this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_UNKNOWN);
return false; return false;
} }
hasher->init();
hasher->add(buf, nonce_len);
hasher->calculate();
this->auth_buf_[0] = this->auth_type_;
hasher->get_hex(buf);
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char log_buf[65]; // Fixed size for SHA256 hex (64) + null, works for MD5 (32) too
memcpy(log_buf, buf, hex_size);
log_buf[hex_size] = '\0';
ESP_LOGV(TAG, "Auth: Nonce is %s", log_buf);
#endif
} }
// Try to write auth_type + nonce // Try to write auth_type + nonce
@@ -678,89 +721,41 @@ bool ESPHomeOTAComponent::handle_auth_read_() {
} }
// We have all the data, verify it // We have all the data, verify it
bool matches = false; const char *nonce = reinterpret_cast<char *>(this->auth_buf_.get() + 1);
const char *cnonce = nonce + hex_size;
const char *response = cnonce + hex_size;
// CRITICAL ESP32-S3: Hash objects must stay in same stack frame (no passing to other functions).
// Declare both hash objects in same stack frame, use pointer to select.
// NOTE: Both objects are declared here even though only one is used. This is REQUIRED for ESP32-S3
// hardware SHA acceleration - the object must exist in this stack frame for all operations.
// Do NOT try to "optimize" by creating the object inside the if block, as it would go out of scope.
#ifdef USE_OTA_SHA256
sha256::SHA256 sha_hasher;
#endif
#ifdef USE_OTA_MD5
md5::MD5Digest md5_hasher;
#endif
HashBase *hasher = nullptr;
#ifdef USE_OTA_SHA256 #ifdef USE_OTA_SHA256
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) { if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) {
sha256::SHA256 sha_hasher; hasher = &sha_hasher;
matches = this->verify_hash_auth_(&sha_hasher, hex_size);
} }
#endif #endif
#ifdef USE_OTA_MD5 #ifdef USE_OTA_MD5
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_AUTH) { if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_AUTH) {
md5::MD5Digest md5_hasher; hasher = &md5_hasher;
matches = this->verify_hash_auth_(&md5_hasher, hex_size);
} }
#endif #endif
if (!matches) {
this->log_auth_warning_(LOG_STR("Password mismatch"));
this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_AUTH_INVALID);
return false;
}
// Authentication successful - clean up auth state
this->cleanup_auth_();
return true;
}
bool ESPHomeOTAComponent::prepare_auth_nonce_(HashBase *hasher) {
// Calculate required buffer size using the hasher
const size_t hex_size = hasher->get_size() * 2;
const size_t nonce_len = hasher->get_size() / 4;
// Buffer layout after AUTH_READ completes:
// [0]: auth_type (1 byte)
// [1...hex_size]: nonce (hex_size bytes) - our random nonce sent in AUTH_SEND
// [1+hex_size...1+2*hex_size-1]: cnonce (hex_size bytes) - client's nonce
// [1+2*hex_size...1+3*hex_size-1]: response (hex_size bytes) - client's hash
// Total: 1 + 3*hex_size
const size_t auth_buf_size = 1 + 3 * hex_size;
this->auth_buf_ = std::make_unique<uint8_t[]>(auth_buf_size);
this->auth_buf_pos_ = 0;
// Generate nonce
char *buf = reinterpret_cast<char *>(this->auth_buf_.get() + 1);
if (!random_bytes(reinterpret_cast<uint8_t *>(buf), nonce_len)) {
this->log_auth_warning_(LOG_STR("Random failed"));
this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_UNKNOWN);
return false;
}
hasher->init();
hasher->add(buf, nonce_len);
hasher->calculate();
// Prepare buffer: auth_type (1 byte) + nonce (hex_size bytes)
this->auth_buf_[0] = this->auth_type_;
hasher->get_hex(buf);
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char log_buf[hex_size + 1];
// Log nonce for debugging
memcpy(log_buf, buf, hex_size);
log_buf[hex_size] = '\0';
ESP_LOGV(TAG, "Auth: Nonce is %s", log_buf);
#endif
return true;
}
bool ESPHomeOTAComponent::verify_hash_auth_(HashBase *hasher, size_t hex_size) {
// Get pointers to the data in the buffer (see prepare_auth_nonce_ for buffer layout)
const char *nonce = reinterpret_cast<char *>(this->auth_buf_.get() + 1); // Skip auth_type byte
const char *cnonce = nonce + hex_size; // CNonce immediately follows nonce
const char *response = cnonce + hex_size; // Response immediately follows cnonce
// Calculate expected hash: password + nonce + cnonce
hasher->init(); hasher->init();
hasher->add(this->password_.c_str(), this->password_.length()); hasher->add(this->password_.c_str(), this->password_.length());
hasher->add(nonce, hex_size * 2); // Add both nonce and cnonce (contiguous in buffer) hasher->add(nonce, hex_size * 2); // Add both nonce and cnonce (contiguous in buffer)
hasher->calculate(); hasher->calculate();
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char log_buf[hex_size + 1]; char log_buf[65]; // Fixed size for SHA256 hex (64) + null, works for MD5 (32) too
// Log CNonce // Log CNonce
memcpy(log_buf, cnonce, hex_size); memcpy(log_buf, cnonce, hex_size);
log_buf[hex_size] = '\0'; log_buf[hex_size] = '\0';
@@ -778,7 +773,18 @@ bool ESPHomeOTAComponent::verify_hash_auth_(HashBase *hasher, size_t hex_size) {
#endif #endif
// Compare response // Compare response
return hasher->equals_hex(response); bool matches = hasher->equals_hex(response);
if (!matches) {
this->log_auth_warning_(LOG_STR("Password mismatch"));
this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_AUTH_INVALID);
return false;
}
// Authentication successful - clean up auth state
this->cleanup_auth_();
return true;
} }
size_t ESPHomeOTAComponent::get_auth_hex_size_() const { size_t ESPHomeOTAComponent::get_auth_hex_size_() const {

View File

@@ -47,8 +47,6 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
bool handle_auth_send_(); bool handle_auth_send_();
bool handle_auth_read_(); bool handle_auth_read_();
bool select_auth_type_(); bool select_auth_type_();
bool prepare_auth_nonce_(HashBase *hasher);
bool verify_hash_auth_(HashBase *hasher, size_t hex_size);
size_t get_auth_hex_size_() const; size_t get_auth_hex_size_() const;
void cleanup_auth_(); void cleanup_auth_();
void log_auth_warning_(const LogString *msg); void log_auth_warning_(const LogString *msg);

View File

@@ -41,17 +41,20 @@ static const char *const TAG = "ethernet";
EthernetComponent *global_eth_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) EthernetComponent *global_eth_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void EthernetComponent::log_error_and_mark_failed_(esp_err_t err, const char *message) {
ESP_LOGE(TAG, "%s: (%d) %s", message, err, esp_err_to_name(err));
this->mark_failed();
}
#define ESPHL_ERROR_CHECK(err, message) \ #define ESPHL_ERROR_CHECK(err, message) \
if ((err) != ESP_OK) { \ if ((err) != ESP_OK) { \
ESP_LOGE(TAG, message ": (%d) %s", err, esp_err_to_name(err)); \ this->log_error_and_mark_failed_(err, message); \
this->mark_failed(); \
return; \ return; \
} }
#define ESPHL_ERROR_CHECK_RET(err, message, ret) \ #define ESPHL_ERROR_CHECK_RET(err, message, ret) \
if ((err) != ESP_OK) { \ if ((err) != ESP_OK) { \
ESP_LOGE(TAG, message ": (%d) %s", err, esp_err_to_name(err)); \ this->log_error_and_mark_failed_(err, message); \
this->mark_failed(); \
return ret; \ return ret; \
} }

View File

@@ -106,6 +106,7 @@ class EthernetComponent : public Component {
void start_connect_(); void start_connect_();
void finish_connect_(); void finish_connect_();
void dump_connect_params_(); void dump_connect_params_();
void log_error_and_mark_failed_(esp_err_t err, const char *message);
#ifdef USE_ETHERNET_KSZ8081 #ifdef USE_ETHERNET_KSZ8081
/// @brief Set `RMII Reference Clock Select` bit for KSZ8081. /// @brief Set `RMII Reference Clock Select` bit for KSZ8081.
void ksz8081_set_clock_reference_(esp_eth_mac_t *mac); void ksz8081_set_clock_reference_(esp_eth_mac_t *mac);
@@ -162,7 +163,7 @@ class EthernetComponent : public Component {
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
extern EthernetComponent *global_eth_component; extern EthernetComponent *global_eth_component;
#if defined(USE_ARDUINO) || ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2)
extern "C" esp_eth_phy_t *esp_eth_phy_new_jl1101(const eth_phy_config_t *config); extern "C" esp_eth_phy_t *esp_eth_phy_new_jl1101(const eth_phy_config_t *config);
#endif #endif

View File

@@ -1,5 +0,0 @@
CODEOWNERS = ["@Rapsssito"]
# Allows event_emitter to be configured in yaml, to allow use of the C++ api.
CONFIG_SCHEMA = {}

View File

@@ -1,117 +0,0 @@
#pragma once
#include <vector>
#include <functional>
#include <limits>
#include "esphome/core/log.h"
namespace esphome {
namespace event_emitter {
using EventEmitterListenerID = uint32_t;
static constexpr EventEmitterListenerID INVALID_LISTENER_ID = 0;
// EventEmitter class that can emit events with a specific name (it is highly recommended to use an enum class for this)
// and a list of arguments. Supports multiple listeners for each event.
template<typename EvtType, typename... Args> class EventEmitter {
public:
EventEmitterListenerID on(EvtType event, std::function<void(Args...)> listener) {
EventEmitterListenerID listener_id = this->get_next_id_();
// Find or create event entry
EventEntry *entry = this->find_or_create_event_(event);
entry->listeners.push_back({listener_id, listener});
return listener_id;
}
void off(EvtType event, EventEmitterListenerID id) {
EventEntry *entry = this->find_event_(event);
if (entry == nullptr)
return;
// Remove listener with given id
for (auto it = entry->listeners.begin(); it != entry->listeners.end(); ++it) {
if (it->id == id) {
// Swap with last and pop for efficient removal
*it = entry->listeners.back();
entry->listeners.pop_back();
// Remove event entry if no more listeners
if (entry->listeners.empty()) {
this->remove_event_(event);
}
return;
}
}
}
protected:
void emit_(EvtType event, Args... args) {
EventEntry *entry = this->find_event_(event);
if (entry == nullptr)
return;
// Call all listeners for this event
for (const auto &listener : entry->listeners) {
listener.callback(args...);
}
}
private:
struct Listener {
EventEmitterListenerID id;
std::function<void(Args...)> callback;
};
struct EventEntry {
EvtType event;
std::vector<Listener> listeners;
};
EventEmitterListenerID get_next_id_() {
// Simple incrementing ID, wrapping around at max
EventEmitterListenerID next_id = (this->current_id_ + 1);
if (next_id == INVALID_LISTENER_ID) {
next_id = 1;
}
this->current_id_ = next_id;
return this->current_id_;
}
EventEntry *find_event_(EvtType event) {
for (auto &entry : this->events_) {
if (entry.event == event) {
return &entry;
}
}
return nullptr;
}
EventEntry *find_or_create_event_(EvtType event) {
EventEntry *entry = this->find_event_(event);
if (entry != nullptr)
return entry;
// Create new event entry
this->events_.push_back({event, {}});
return &this->events_.back();
}
void remove_event_(EvtType event) {
for (auto it = this->events_.begin(); it != this->events_.end(); ++it) {
if (it->event == event) {
// Swap with last and pop
*it = this->events_.back();
this->events_.pop_back();
return;
}
}
}
std::vector<EventEntry> events_;
EventEmitterListenerID current_id_ = 0;
};
} // namespace event_emitter
} // namespace esphome

View File

@@ -80,7 +80,7 @@ void FingerprintGrowComponent::setup() {
delay(20); // This delay guarantees the sensor will in fact be powered power. delay(20); // This delay guarantees the sensor will in fact be powered power.
if (this->check_password_()) { if (this->check_password_()) {
if (this->new_password_ != -1) { if (this->new_password_ != std::numeric_limits<uint32_t>::max()) {
if (this->set_password_()) if (this->set_password_())
return; return;
} else { } else {

View File

@@ -6,6 +6,7 @@
#include "esphome/components/binary_sensor/binary_sensor.h" #include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/uart/uart.h" #include "esphome/components/uart/uart.h"
#include <limits>
#include <vector> #include <vector>
namespace esphome { namespace esphome {
@@ -177,7 +178,7 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic
uint8_t address_[4] = {0xFF, 0xFF, 0xFF, 0xFF}; uint8_t address_[4] = {0xFF, 0xFF, 0xFF, 0xFF};
uint16_t capacity_ = 64; uint16_t capacity_ = 64;
uint32_t password_ = 0x0; uint32_t password_ = 0x0;
uint32_t new_password_ = -1; uint32_t new_password_ = std::numeric_limits<uint32_t>::max();
GPIOPin *sensing_pin_{nullptr}; GPIOPin *sensing_pin_{nullptr};
GPIOPin *sensor_power_pin_{nullptr}; GPIOPin *sensor_power_pin_{nullptr};
uint8_t enrollment_image_ = 0; uint8_t enrollment_image_ = 0;

View File

@@ -179,7 +179,7 @@ void Graph::draw(Display *buff, uint16_t x_offset, uint16_t y_offset, Color colo
if (b) { if (b) {
int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2 + y_offset; int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2 + y_offset;
auto draw_pixel_at = [&buff, c, y_offset, this](int16_t x, int16_t y) { auto draw_pixel_at = [&buff, c, y_offset, this](int16_t x, int16_t y) {
if (y >= y_offset && y < y_offset + this->height_) if (y >= y_offset && static_cast<uint32_t>(y) < y_offset + this->height_)
buff->draw_pixel_at(x, y, c); buff->draw_pixel_at(x, y, c);
}; };
if (!continuous || !has_prev || !prev_b || (abs(y - prev_y) <= thick)) { if (!continuous || !has_prev || !prev_b || (abs(y - prev_y) <= thick)) {

View File

@@ -116,7 +116,7 @@ void GraphicalDisplayMenu::draw_menu_internal_(display::Display *display, const
int number_items_fit_to_screen = 0; int number_items_fit_to_screen = 0;
const int max_item_index = this->displayed_item_->items_size() - 1; const int max_item_index = this->displayed_item_->items_size() - 1;
for (size_t i = 0; i <= max_item_index; i++) { for (size_t i = 0; max_item_index >= 0 && i <= static_cast<size_t>(max_item_index); i++) {
const auto *item = this->displayed_item_->get_item(i); const auto *item = this->displayed_item_->get_item(i);
const bool selected = i == this->cursor_index_; const bool selected = i == this->cursor_index_;
const display::Rect item_dimensions = this->measure_item(display, item, bounds, selected); const display::Rect item_dimensions = this->measure_item(display, item, bounds, selected);
@@ -174,7 +174,8 @@ void GraphicalDisplayMenu::draw_menu_internal_(display::Display *display, const
display->filled_rectangle(bounds->x, bounds->y, max_width, total_height, this->background_color_); display->filled_rectangle(bounds->x, bounds->y, max_width, total_height, this->background_color_);
auto y_offset = bounds->y; auto y_offset = bounds->y;
for (size_t i = first_item_index; i <= last_item_index; i++) { for (size_t i = static_cast<size_t>(first_item_index);
last_item_index >= 0 && i <= static_cast<size_t>(last_item_index); i++) {
const auto *item = this->displayed_item_->get_item(i); const auto *item = this->displayed_item_->get_item(i);
const bool selected = i == this->cursor_index_; const bool selected = i == this->cursor_index_;
display::Rect dimensions = menu_dimensions[i]; display::Rect dimensions = menu_dimensions[i];

View File

@@ -213,7 +213,7 @@ haier_protocol::HandlerError HonClimate::status_handler_(haier_protocol::FrameTy
this->real_control_packet_size_); this->real_control_packet_size_);
this->status_message_callback_.call((const char *) data, data_size); this->status_message_callback_.call((const char *) data, data_size);
} else { } else {
ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, this->real_control_packet_size_); ESP_LOGW(TAG, "Status packet too small: %zu (should be >= %zu)", data_size, this->real_control_packet_size_);
} }
switch (this->protocol_phase_) { switch (this->protocol_phase_) {
case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST:
@@ -827,7 +827,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
size_t expected_size = size_t expected_size =
2 + this->status_message_header_size_ + this->real_control_packet_size_ + this->real_sensors_packet_size_; 2 + this->status_message_header_size_ + this->real_control_packet_size_ + this->real_sensors_packet_size_;
if (size < expected_size) { if (size < expected_size) {
ESP_LOGW(TAG, "Unexpected message size %d (expexted >= %d)", size, expected_size); ESP_LOGW(TAG, "Unexpected message size %u (expexted >= %zu)", size, expected_size);
return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
} }
uint16_t subtype = (((uint16_t) packet_buffer[0]) << 8) + packet_buffer[1]; uint16_t subtype = (((uint16_t) packet_buffer[0]) << 8) + packet_buffer[1];

View File

@@ -178,7 +178,7 @@ class HonClimate : public HaierClimateBase {
int extra_control_packet_bytes_{0}; int extra_control_packet_bytes_{0};
int extra_sensors_packet_bytes_{4}; int extra_sensors_packet_bytes_{4};
int status_message_header_size_{0}; int status_message_header_size_{0};
int real_control_packet_size_{sizeof(hon_protocol::HaierPacketControl)}; size_t real_control_packet_size_{sizeof(hon_protocol::HaierPacketControl)};
int real_sensors_packet_size_{sizeof(hon_protocol::HaierPacketSensors) + 4}; int real_sensors_packet_size_{sizeof(hon_protocol::HaierPacketSensors) + 4};
HonControlMethod control_method_; HonControlMethod control_method_;
std::queue<haier_protocol::HaierMessage> control_messages_queue_; std::queue<haier_protocol::HaierMessage> control_messages_queue_;

View File

@@ -7,24 +7,20 @@ namespace hdc1080 {
static const char *const TAG = "hdc1080"; static const char *const TAG = "hdc1080";
static const uint8_t HDC1080_ADDRESS = 0x40; // 0b1000000 from datasheet
static const uint8_t HDC1080_CMD_CONFIGURATION = 0x02; static const uint8_t HDC1080_CMD_CONFIGURATION = 0x02;
static const uint8_t HDC1080_CMD_TEMPERATURE = 0x00; static const uint8_t HDC1080_CMD_TEMPERATURE = 0x00;
static const uint8_t HDC1080_CMD_HUMIDITY = 0x01; static const uint8_t HDC1080_CMD_HUMIDITY = 0x01;
void HDC1080Component::setup() { void HDC1080Component::setup() {
const uint8_t data[2] = { const uint8_t config[2] = {0x00, 0x00}; // resolution 14bit for both humidity and temperature
0b00000000, // resolution 14bit for both humidity and temperature
0b00000000 // reserved
};
if (!this->write_bytes(HDC1080_CMD_CONFIGURATION, data, 2)) { // if configuration fails - there is a problem
// as instruction is same as powerup defaults (for now), interpret as warning if this fails if (this->write_register(HDC1080_CMD_CONFIGURATION, config, 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "HDC1080 initial config instruction error"); this->mark_failed();
this->status_set_warning();
return; return;
} }
} }
void HDC1080Component::dump_config() { void HDC1080Component::dump_config() {
ESP_LOGCONFIG(TAG, "HDC1080:"); ESP_LOGCONFIG(TAG, "HDC1080:");
LOG_I2C_DEVICE(this); LOG_I2C_DEVICE(this);
@@ -35,39 +31,51 @@ void HDC1080Component::dump_config() {
LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Humidity", this->humidity_);
} }
void HDC1080Component::update() { void HDC1080Component::update() {
uint16_t raw_temp; // regardless of what sensor/s are defined in yaml configuration
// the hdc1080 setup configuration used, requires both temperature and humidity to be read
this->status_clear_warning();
if (this->write(&HDC1080_CMD_TEMPERATURE, 1) != i2c::ERROR_OK) { if (this->write(&HDC1080_CMD_TEMPERATURE, 1) != i2c::ERROR_OK) {
this->status_set_warning(); this->status_set_warning();
return; return;
} }
delay(20);
if (this->read(reinterpret_cast<uint8_t *>(&raw_temp), 2) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}
raw_temp = i2c::i2ctohs(raw_temp);
float temp = raw_temp * 0.0025177f - 40.0f; // raw * 2^-16 * 165 - 40
this->temperature_->publish_state(temp);
uint16_t raw_humidity; this->set_timeout(20, [this]() {
if (this->write(&HDC1080_CMD_HUMIDITY, 1) != i2c::ERROR_OK) { uint16_t raw_temperature;
this->status_set_warning(); if (this->read(reinterpret_cast<uint8_t *>(&raw_temperature), 2) != i2c::ERROR_OK) {
return; this->status_set_warning();
} return;
delay(20); }
if (this->read(reinterpret_cast<uint8_t *>(&raw_humidity), 2) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}
raw_humidity = i2c::i2ctohs(raw_humidity);
float humidity = raw_humidity * 0.001525879f; // raw * 2^-16 * 100
this->humidity_->publish_state(humidity);
ESP_LOGD(TAG, "Got temperature=%.1f°C humidity=%.1f%%", temp, humidity); if (this->temperature_ != nullptr) {
this->status_clear_warning(); raw_temperature = i2c::i2ctohs(raw_temperature);
float temperature = raw_temperature * 0.0025177f - 40.0f; // raw * 2^-16 * 165 - 40
this->temperature_->publish_state(temperature);
}
if (this->write(&HDC1080_CMD_HUMIDITY, 1) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}
this->set_timeout(20, [this]() {
uint16_t raw_humidity;
if (this->read(reinterpret_cast<uint8_t *>(&raw_humidity), 2) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}
if (this->humidity_ != nullptr) {
raw_humidity = i2c::i2ctohs(raw_humidity);
float humidity = raw_humidity * 0.001525879f; // raw * 2^-16 * 100
this->humidity_->publish_state(humidity);
}
});
});
} }
float HDC1080Component::get_setup_priority() const { return setup_priority::DATA; }
} // namespace hdc1080 } // namespace hdc1080
} // namespace esphome } // namespace esphome

View File

@@ -12,13 +12,11 @@ class HDC1080Component : public PollingComponent, public i2c::I2CDevice {
void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
/// Setup the sensor and check for connection.
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
/// Retrieve the latest sensor values. This operation takes approximately 16ms.
void update() override; void update() override;
float get_setup_priority() const override; float get_setup_priority() const override { return setup_priority::DATA; }
protected: protected:
sensor::Sensor *temperature_{nullptr}; sensor::Sensor *temperature_{nullptr};

View File

@@ -377,7 +377,7 @@ void I2SAudioSpeaker::speaker_task(void *params) {
this_speaker->current_stream_info_.get_bits_per_sample() <= 16) { this_speaker->current_stream_info_.get_bits_per_sample() <= 16) {
size_t len = bytes_read / sizeof(int16_t); size_t len = bytes_read / sizeof(int16_t);
int16_t *tmp_buf = (int16_t *) new_data; int16_t *tmp_buf = (int16_t *) new_data;
for (int i = 0; i < len; i += 2) { for (size_t i = 0; i < len; i += 2) {
int16_t tmp = tmp_buf[i]; int16_t tmp = tmp_buf[i];
tmp_buf[i] = tmp_buf[i + 1]; tmp_buf[i] = tmp_buf[i + 1];
tmp_buf[i + 1] = tmp; tmp_buf[i + 1] = tmp;

View File

@@ -325,7 +325,7 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons
// we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't bother // we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't bother
this->write_array(ptr, w * h * 2); this->write_array(ptr, w * h * 2);
} else { } else {
for (size_t y = 0; y != h; y++) { for (size_t y = 0; y != static_cast<size_t>(h); y++) {
this->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2); this->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2);
} }
} }
@@ -349,7 +349,7 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons
App.feed_wdt(); App.feed_wdt();
} }
// end of line? Skip to the next. // end of line? Skip to the next.
if (++pixel == w) { if (++pixel == static_cast<size_t>(w)) {
pixel = 0; pixel = 0;
ptr += (x_pad + x_offset) * 2; ptr += (x_pad + x_offset) * 2;
} }

View File

@@ -19,15 +19,19 @@ std::string build_json(const json_build_t &f) {
bool parse_json(const std::string &data, const json_parse_t &f) { bool parse_json(const std::string &data, const json_parse_t &f) {
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
JsonDocument doc = parse_json(data); JsonDocument doc = parse_json(reinterpret_cast<const uint8_t *>(data.c_str()), data.size());
if (doc.overflowed() || doc.isNull()) if (doc.overflowed() || doc.isNull())
return false; return false;
return f(doc.as<JsonObject>()); return f(doc.as<JsonObject>());
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
} }
JsonDocument parse_json(const std::string &data) { JsonDocument parse_json(const uint8_t *data, size_t len) {
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
if (data == nullptr || len == 0) {
ESP_LOGE(TAG, "No data to parse");
return JsonObject(); // return unbound object
}
#ifdef USE_PSRAM #ifdef USE_PSRAM
auto doc_allocator = SpiRamAllocator(); auto doc_allocator = SpiRamAllocator();
JsonDocument json_document(&doc_allocator); JsonDocument json_document(&doc_allocator);
@@ -38,7 +42,7 @@ JsonDocument parse_json(const std::string &data) {
ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); ESP_LOGE(TAG, "Could not allocate memory for JSON document!");
return JsonObject(); // return unbound object return JsonObject(); // return unbound object
} }
DeserializationError err = deserializeJson(json_document, data); DeserializationError err = deserializeJson(json_document, data, len);
if (err == DeserializationError::Ok) { if (err == DeserializationError::Ok) {
return json_document; return json_document;

View File

@@ -2,6 +2,7 @@
#include <vector> #include <vector>
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#define ARDUINOJSON_ENABLE_STD_STRING 1 // NOLINT #define ARDUINOJSON_ENABLE_STD_STRING 1 // NOLINT
@@ -49,8 +50,13 @@ std::string build_json(const json_build_t &f);
/// Parse a JSON string and run the provided json parse function if it's valid. /// Parse a JSON string and run the provided json parse function if it's valid.
bool parse_json(const std::string &data, const json_parse_t &f); bool parse_json(const std::string &data, const json_parse_t &f);
/// Parse a JSON string and return the root JsonDocument (or an unbound object on error) /// Parse a JSON string and return the root JsonDocument (or an unbound object on error)
JsonDocument parse_json(const std::string &data); JsonDocument parse_json(const uint8_t *data, size_t len);
/// Parse a JSON string and return the root JsonDocument (or an unbound object on error)
inline JsonDocument parse_json(const std::string &data) {
return parse_json(reinterpret_cast<const uint8_t *>(data.c_str()), data.size());
}
/// Builder class for creating JSON documents without lambdas /// Builder class for creating JSON documents without lambdas
class JsonBuilder { class JsonBuilder {

View File

@@ -22,7 +22,7 @@ void KamstrupKMPComponent::dump_config() {
LOG_SENSOR(" ", "Flow", this->flow_sensor_); LOG_SENSOR(" ", "Flow", this->flow_sensor_);
LOG_SENSOR(" ", "Volume", this->volume_sensor_); LOG_SENSOR(" ", "Volume", this->volume_sensor_);
for (int i = 0; i < this->custom_sensors_.size(); i++) { for (size_t i = 0; i < this->custom_sensors_.size(); i++) {
LOG_SENSOR(" ", "Custom Sensor", this->custom_sensors_[i]); LOG_SENSOR(" ", "Custom Sensor", this->custom_sensors_[i]);
ESP_LOGCONFIG(TAG, " Command: 0x%04X", this->custom_commands_[i]); ESP_LOGCONFIG(TAG, " Command: 0x%04X", this->custom_commands_[i]);
} }
@@ -268,7 +268,7 @@ void KamstrupKMPComponent::set_sensor_value_(uint16_t command, float value, uint
} }
// Custom sensors // Custom sensors
for (int i = 0; i < this->custom_commands_.size(); i++) { for (size_t i = 0; i < this->custom_commands_.size(); i++) {
if (command == this->custom_commands_[i]) { if (command == this->custom_commands_[i]) {
this->custom_sensors_[i]->publish_state(value); this->custom_sensors_[i]->publish_state(value);
} }

View File

@@ -13,8 +13,8 @@ class KeyCollector : public Component {
void loop() override; void loop() override;
void dump_config() override; void dump_config() override;
void set_provider(key_provider::KeyProvider *provider); void set_provider(key_provider::KeyProvider *provider);
void set_min_length(int min_length) { this->min_length_ = min_length; }; void set_min_length(uint32_t min_length) { this->min_length_ = min_length; };
void set_max_length(int max_length) { this->max_length_ = max_length; }; void set_max_length(uint32_t max_length) { this->max_length_ = max_length; };
void set_start_keys(std::string start_keys) { this->start_keys_ = std::move(start_keys); }; void set_start_keys(std::string start_keys) { this->start_keys_ = std::move(start_keys); };
void set_end_keys(std::string end_keys) { this->end_keys_ = std::move(end_keys); }; void set_end_keys(std::string end_keys) { this->end_keys_ = std::move(end_keys); };
void set_end_key_required(bool end_key_required) { this->end_key_required_ = end_key_required; }; void set_end_key_required(bool end_key_required) { this->end_key_required_ = end_key_required; };
@@ -33,8 +33,8 @@ class KeyCollector : public Component {
protected: protected:
void key_pressed_(uint8_t key); void key_pressed_(uint8_t key);
int min_length_{0}; uint32_t min_length_{0};
int max_length_{0}; uint32_t max_length_{0};
std::string start_keys_; std::string start_keys_;
std::string end_keys_; std::string end_keys_;
bool end_key_required_{false}; bool end_key_required_{false};

View File

View File

@@ -0,0 +1,39 @@
#include "lm75b.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace lm75b {
static const char *const TAG = "lm75b";
void LM75BComponent::dump_config() {
ESP_LOGCONFIG(TAG, "LM75B:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Setting up LM75B failed!");
}
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Temperature", this);
}
void LM75BComponent::update() {
// Create a temporary buffer
uint8_t buff[2];
if (this->read_register(LM75B_REG_TEMPERATURE, buff, 2) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}
// Obtain combined 16-bit value
int16_t raw_temperature = (buff[0] << 8) | buff[1];
// Read the 11-bit raw temperature value
raw_temperature >>= 5;
// Publish the temperature in °C
this->publish_state(raw_temperature * 0.125);
if (this->status_has_warning()) {
this->status_clear_warning();
}
}
} // namespace lm75b
} // namespace esphome

View File

@@ -0,0 +1,19 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace lm75b {
static const uint8_t LM75B_REG_TEMPERATURE = 0x00;
class LM75BComponent : public PollingComponent, public i2c::I2CDevice, public sensor::Sensor {
public:
void dump_config() override;
void update() override;
};
} // namespace lm75b
} // namespace esphome

View File

@@ -0,0 +1,34 @@
import esphome.codegen as cg
from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import (
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
)
CODEOWNERS = ["@beormund"]
DEPENDENCIES = ["i2c"]
lm75b_ns = cg.esphome_ns.namespace("lm75b")
LM75BComponent = lm75b_ns.class_(
"LM75BComponent", cg.PollingComponent, i2c.I2CDevice, sensor.Sensor
)
CONFIG_SCHEMA = (
sensor.sensor_schema(
LM75BComponent,
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x48))
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)

View File

@@ -5,7 +5,7 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
#include <set> #include <initializer_list>
namespace esphome { namespace esphome {
namespace lock { namespace lock {
@@ -44,16 +44,22 @@ class LockTraits {
bool get_assumed_state() const { return this->assumed_state_; } bool get_assumed_state() const { return this->assumed_state_; }
void set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; } void set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
bool supports_state(LockState state) const { return supported_states_.count(state); } bool supports_state(LockState state) const { return supported_states_mask_ & (1 << state); }
std::set<LockState> get_supported_states() const { return supported_states_; } void set_supported_states(std::initializer_list<LockState> states) {
void set_supported_states(std::set<LockState> states) { supported_states_ = std::move(states); } supported_states_mask_ = 0;
void add_supported_state(LockState state) { supported_states_.insert(state); } for (auto state : states) {
supported_states_mask_ |= (1 << state);
}
}
uint8_t get_supported_states_mask() const { return supported_states_mask_; }
void set_supported_states_mask(uint8_t mask) { supported_states_mask_ = mask; }
void add_supported_state(LockState state) { supported_states_mask_ |= (1 << state); }
protected: protected:
bool supports_open_{false}; bool supports_open_{false};
bool requires_code_{false}; bool requires_code_{false};
bool assumed_state_{false}; bool assumed_state_{false};
std::set<LockState> supported_states_ = {LOCK_STATE_NONE, LOCK_STATE_LOCKED, LOCK_STATE_UNLOCKED}; uint8_t supported_states_mask_{(1 << LOCK_STATE_NONE) | (1 << LOCK_STATE_LOCKED) | (1 << LOCK_STATE_UNLOCKED)};
}; };
/** This class is used to encode all control actions on a lock device. /** This class is used to encode all control actions on a lock device.

View File

@@ -95,6 +95,7 @@ DEFAULT = "DEFAULT"
CONF_INITIAL_LEVEL = "initial_level" CONF_INITIAL_LEVEL = "initial_level"
CONF_LOGGER_ID = "logger_id" CONF_LOGGER_ID = "logger_id"
CONF_RUNTIME_TAG_LEVELS = "runtime_tag_levels"
CONF_TASK_LOG_BUFFER_SIZE = "task_log_buffer_size" CONF_TASK_LOG_BUFFER_SIZE = "task_log_buffer_size"
UART_SELECTION_ESP32 = { UART_SELECTION_ESP32 = {
@@ -249,6 +250,7 @@ CONFIG_SCHEMA = cv.All(
} }
), ),
cv.Optional(CONF_INITIAL_LEVEL): is_log_level, cv.Optional(CONF_INITIAL_LEVEL): is_log_level,
cv.Optional(CONF_RUNTIME_TAG_LEVELS, default=False): cv.boolean,
cv.Optional(CONF_ON_MESSAGE): automation.validate_automation( cv.Optional(CONF_ON_MESSAGE): automation.validate_automation(
{ {
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LoggerMessageTrigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LoggerMessageTrigger),
@@ -291,8 +293,12 @@ async def to_code(config):
) )
cg.add(log.pre_setup()) cg.add(log.pre_setup())
for tag, log_level in config[CONF_LOGS].items(): # Enable runtime tag levels if logs are configured or explicitly enabled
cg.add(log.set_log_level(tag, LOG_LEVELS[log_level])) logs_config = config[CONF_LOGS]
if logs_config or config[CONF_RUNTIME_TAG_LEVELS]:
cg.add_define("USE_LOGGER_RUNTIME_TAG_LEVELS")
for tag, log_level in logs_config.items():
cg.add(log.set_log_level(tag, LOG_LEVELS[log_level]))
cg.add_define("USE_LOGGER") cg.add_define("USE_LOGGER")
this_severity = LOG_LEVEL_SEVERITY.index(level) this_severity = LOG_LEVEL_SEVERITY.index(level)
@@ -443,6 +449,7 @@ async def logger_set_level_to_code(config, action_id, template_arg, args):
level = LOG_LEVELS[config[CONF_LEVEL]] level = LOG_LEVELS[config[CONF_LEVEL]]
logger = await cg.get_variable(config[CONF_LOGGER_ID]) logger = await cg.get_variable(config[CONF_LOGGER_ID])
if tag := config.get(CONF_TAG): if tag := config.get(CONF_TAG):
cg.add_define("USE_LOGGER_RUNTIME_TAG_LEVELS")
text = str(cg.statement(logger.set_log_level(tag, level))) text = str(cg.statement(logger.set_log_level(tag, level)))
else: else:
text = str(cg.statement(logger.set_log_level(level))) text = str(cg.statement(logger.set_log_level(level)))

View File

@@ -148,9 +148,11 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
#endif // USE_STORE_LOG_STR_IN_FLASH #endif // USE_STORE_LOG_STR_IN_FLASH
inline uint8_t Logger::level_for(const char *tag) { inline uint8_t Logger::level_for(const char *tag) {
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
auto it = this->log_levels_.find(tag); auto it = this->log_levels_.find(tag);
if (it != this->log_levels_.end()) if (it != this->log_levels_.end())
return it->second; return it->second;
#endif
return this->current_level_; return this->current_level_;
} }
@@ -220,7 +222,9 @@ void Logger::process_messages_() {
} }
void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; } void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; }
void Logger::set_log_level(const std::string &tag, uint8_t log_level) { this->log_levels_[tag] = log_level; } #ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
void Logger::set_log_level(const char *tag, uint8_t log_level) { this->log_levels_[tag] = log_level; }
#endif
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
UARTSelection Logger::get_uart() const { return this->uart_; } UARTSelection Logger::get_uart() const { return this->uart_; }
@@ -271,9 +275,11 @@ void Logger::dump_config() {
} }
#endif #endif
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
for (auto &it : this->log_levels_) { for (auto &it : this->log_levels_) {
ESP_LOGCONFIG(TAG, " Level for '%s': %s", it.first.c_str(), LOG_STR_ARG(LOG_LEVELS[it.second])); ESP_LOGCONFIG(TAG, " Level for '%s': %s", it.first, LOG_STR_ARG(LOG_LEVELS[it.second]));
} }
#endif
} }
void Logger::set_log_level(uint8_t level) { void Logger::set_log_level(uint8_t level) {

View File

@@ -36,29 +36,38 @@ struct device;
namespace esphome::logger { namespace esphome::logger {
// Color and letter constants for log levels #ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
static const char *const LOG_LEVEL_COLORS[] = { // Comparison function for const char* keys in log_levels_ map
"", // NONE struct CStrCompare {
ESPHOME_LOG_BOLD(ESPHOME_LOG_COLOR_RED), // ERROR bool operator()(const char *a, const char *b) const { return strcmp(a, b) < 0; }
ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_YELLOW), // WARNING };
ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_GREEN), // INFO #endif
ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_MAGENTA), // CONFIG
ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_CYAN), // DEBUG // ANSI color code last digit (30-38 range, store only last digit to save RAM)
ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_GRAY), // VERBOSE static constexpr char LOG_LEVEL_COLOR_DIGIT[] = {
ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_WHITE), // VERY_VERBOSE '\0', // NONE
'1', // ERROR (31 = red)
'3', // WARNING (33 = yellow)
'2', // INFO (32 = green)
'5', // CONFIG (35 = magenta)
'6', // DEBUG (36 = cyan)
'7', // VERBOSE (37 = gray)
'8', // VERY_VERBOSE (38 = white)
}; };
static const char *const LOG_LEVEL_LETTERS[] = { static constexpr char LOG_LEVEL_LETTER_CHARS[] = {
"", // NONE '\0', // NONE
"E", // ERROR 'E', // ERROR
"W", // WARNING 'W', // WARNING
"I", // INFO 'I', // INFO
"C", // CONFIG 'C', // CONFIG
"D", // DEBUG 'D', // DEBUG
"V", // VERBOSE 'V', // VERBOSE (VERY_VERBOSE uses two 'V's)
"VV", // VERY_VERBOSE
}; };
// Maximum header size: 35 bytes fixed + 32 bytes tag + 16 bytes thread name = 83 bytes (45 byte safety margin)
static constexpr uint16_t MAX_HEADER_SIZE = 128;
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
/** Enum for logging UART selection /** Enum for logging UART selection
* *
@@ -131,8 +140,10 @@ class Logger : public Component {
/// Set the default log level for this logger. /// Set the default log level for this logger.
void set_log_level(uint8_t level); void set_log_level(uint8_t level);
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
/// Set the log level of the specified tag. /// Set the log level of the specified tag.
void set_log_level(const std::string &tag, uint8_t log_level); void set_log_level(const char *tag, uint8_t log_level);
#endif
uint8_t get_log_level() { return this->current_level_; } uint8_t get_log_level() { return this->current_level_; }
// ========== INTERNAL METHODS ========== // ========== INTERNAL METHODS ==========
@@ -215,14 +226,6 @@ class Logger : public Component {
} }
} }
// Format string to explicit buffer with varargs
inline void printf_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->format_body_to_buffer_(buffer, buffer_at, buffer_size, format, arg);
va_end(arg);
}
#ifndef USE_HOST #ifndef USE_HOST
const LogString *get_uart_selection_(); const LogString *get_uart_selection_();
#endif #endif
@@ -248,7 +251,9 @@ class Logger : public Component {
#endif #endif
// Large objects (internally aligned) // Large objects (internally aligned)
std::map<std::string, uint8_t> log_levels_{}; #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, const char *, const char *, size_t)> log_callback_{};
CallbackManager<void(uint8_t)> level_callback_{}; CallbackManager<void(uint8_t)> level_callback_{};
#ifdef USE_ESPHOME_TASK_LOG_BUFFER #ifdef USE_ESPHOME_TASK_LOG_BUFFER
@@ -318,26 +323,76 @@ class Logger : public Component {
} }
#endif #endif
static inline void copy_string(char *buffer, uint16_t &pos, const char *str) {
const size_t len = strlen(str);
// Intentionally no null terminator, building larger string
memcpy(buffer + pos, str, len); // NOLINT(bugprone-not-null-terminated-result)
pos += len;
}
static inline void write_ansi_color_for_level(char *buffer, uint16_t &pos, uint8_t level) {
if (level == 0)
return;
// Construct ANSI escape sequence: "\033[{bold};3{color}m"
// Example: "\033[1;31m" for ERROR (bold red)
buffer[pos++] = '\033';
buffer[pos++] = '[';
buffer[pos++] = (level == 1) ? '1' : '0'; // Only ERROR is bold
buffer[pos++] = ';';
buffer[pos++] = '3';
buffer[pos++] = LOG_LEVEL_COLOR_DIGIT[level];
buffer[pos++] = 'm';
}
inline void HOT write_header_to_buffer_(uint8_t level, const char *tag, int line, const char *thread_name, inline void HOT write_header_to_buffer_(uint8_t level, const char *tag, int line, const char *thread_name,
char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
// Format header uint16_t pos = *buffer_at;
// uint8_t level is already bounded 0-255, just ensure it's <= 7 // Early return if insufficient space - intentionally don't update buffer_at to prevent partial writes
if (level > 7) if (pos + MAX_HEADER_SIZE > buffer_size)
level = 7; return;
const char *color = esphome::logger::LOG_LEVEL_COLORS[level]; // Construct: <color>[LEVEL][tag:line]:
const char *letter = esphome::logger::LOG_LEVEL_LETTERS[level]; write_ansi_color_for_level(buffer, pos, level);
buffer[pos++] = '[';
if (level != 0) {
if (level >= 7) {
buffer[pos++] = 'V'; // VERY_VERBOSE = "VV"
buffer[pos++] = 'V';
} else {
buffer[pos++] = LOG_LEVEL_LETTER_CHARS[level];
}
}
buffer[pos++] = ']';
buffer[pos++] = '[';
copy_string(buffer, pos, tag);
buffer[pos++] = ':';
// Format line number without modulo operations (passed by value, safe to mutate)
if (line > 999) [[unlikely]] {
int thousands = line / 1000;
buffer[pos++] = '0' + thousands;
line -= thousands * 1000;
}
int hundreds = line / 100;
int remainder = line - hundreds * 100;
int tens = remainder / 10;
buffer[pos++] = '0' + hundreds;
buffer[pos++] = '0' + tens;
buffer[pos++] = '0' + (remainder - tens * 10);
buffer[pos++] = ']';
#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) #if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
if (thread_name != nullptr) { if (thread_name != nullptr) {
// Non-main task with thread name write_ansi_color_for_level(buffer, pos, 1); // Always use bold red for thread name
this->printf_to_buffer_(buffer, buffer_at, buffer_size, "%s[%s][%s:%03u]%s[%s]%s: ", color, letter, tag, line, buffer[pos++] = '[';
ESPHOME_LOG_BOLD(ESPHOME_LOG_COLOR_RED), thread_name, color); copy_string(buffer, pos, thread_name);
return; buffer[pos++] = ']';
write_ansi_color_for_level(buffer, pos, level); // Restore original color
} }
#endif #endif
// Main task or non ESP32/LibreTiny platform
this->printf_to_buffer_(buffer, buffer_at, buffer_size, "%s[%s][%s:%03u]: ", color, letter, tag, line); buffer[pos++] = ':';
buffer[pos++] = ' ';
*buffer_at = pos;
} }
inline void HOT format_body_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format, inline void HOT format_body_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format,

View File

@@ -3,11 +3,10 @@
namespace esphome::logger { namespace esphome::logger {
void LoggerLevelSelect::publish_state(int level) { void LoggerLevelSelect::publish_state(int level) {
auto value = this->at(level); const auto &option = this->at(level_to_index(level));
if (!value) { if (!option)
return; return;
} Select::publish_state(option.value());
Select::publish_state(value.value());
} }
void LoggerLevelSelect::setup() { void LoggerLevelSelect::setup() {
@@ -16,10 +15,10 @@ void LoggerLevelSelect::setup() {
} }
void LoggerLevelSelect::control(const std::string &value) { void LoggerLevelSelect::control(const std::string &value) {
auto level = this->index_of(value); const auto index = this->index_of(value);
if (!level) if (!index)
return; return;
this->parent_->set_log_level(level.value()); this->parent_->set_log_level(index_to_level(index.value()));
} }
} // namespace esphome::logger } // namespace esphome::logger

View File

@@ -3,11 +3,18 @@
#include "esphome/components/select/select.h" #include "esphome/components/select/select.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/components/logger/logger.h" #include "esphome/components/logger/logger.h"
namespace esphome::logger { namespace esphome::logger {
class LoggerLevelSelect : public Component, public select::Select, public Parented<Logger> { class LoggerLevelSelect : public Component, public select::Select, public Parented<Logger> {
public: public:
void publish_state(int level); void publish_state(int level);
void setup() override; void setup() override;
void control(const std::string &value) override; void control(const std::string &value) 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; }
// Convert option index to log level (skip CONFIG at level 4)
static uint8_t index_to_level(uint8_t index) { return (index >= ESPHOME_LOG_LEVEL_CONFIG) ? index + 1 : index; }
}; };
} // namespace esphome::logger } // namespace esphome::logger

View File

@@ -2,6 +2,7 @@
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include <limits>
using esphome::i2c::ErrorCode; using esphome::i2c::ErrorCode;
@@ -28,30 +29,30 @@ bool operator!=(const GainTimePair &lhs, const GainTimePair &rhs) {
template<typename T, size_t size> T get_next(const T (&array)[size], const T val) { template<typename T, size_t size> T get_next(const T (&array)[size], const T val) {
size_t i = 0; size_t i = 0;
size_t idx = -1; size_t idx = std::numeric_limits<size_t>::max();
while (idx == -1 && i < size) { while (idx == std::numeric_limits<size_t>::max() && i < size) {
if (array[i] == val) { if (array[i] == val) {
idx = i; idx = i;
break; break;
} }
i++; i++;
} }
if (idx == -1 || i + 1 >= size) if (idx == std::numeric_limits<size_t>::max() || i + 1 >= size)
return val; return val;
return array[i + 1]; return array[i + 1];
} }
template<typename T, size_t size> T get_prev(const T (&array)[size], const T val) { template<typename T, size_t size> T get_prev(const T (&array)[size], const T val) {
size_t i = size - 1; size_t i = size - 1;
size_t idx = -1; size_t idx = std::numeric_limits<size_t>::max();
while (idx == -1 && i > 0) { while (idx == std::numeric_limits<size_t>::max() && i > 0) {
if (array[i] == val) { if (array[i] == val) {
idx = i; idx = i;
break; break;
} }
i--; i--;
} }
if (idx == -1 || i == 0) if (idx == std::numeric_limits<size_t>::max() || i == 0)
return val; return val;
return array[i - 1]; return array[i - 1];
} }

View File

@@ -2,6 +2,7 @@
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include <limits>
using esphome::i2c::ErrorCode; using esphome::i2c::ErrorCode;
@@ -14,30 +15,30 @@ static const uint8_t MAX_TRIES = 5;
template<typename T, size_t size> T get_next(const T (&array)[size], const T val) { template<typename T, size_t size> T get_next(const T (&array)[size], const T val) {
size_t i = 0; size_t i = 0;
size_t idx = -1; size_t idx = std::numeric_limits<size_t>::max();
while (idx == -1 && i < size) { while (idx == std::numeric_limits<size_t>::max() && i < size) {
if (array[i] == val) { if (array[i] == val) {
idx = i; idx = i;
break; break;
} }
i++; i++;
} }
if (idx == -1 || i + 1 >= size) if (idx == std::numeric_limits<size_t>::max() || i + 1 >= size)
return val; return val;
return array[i + 1]; return array[i + 1];
} }
template<typename T, size_t size> T get_prev(const T (&array)[size], const T val) { template<typename T, size_t size> T get_prev(const T (&array)[size], const T val) {
size_t i = size - 1; size_t i = size - 1;
size_t idx = -1; size_t idx = std::numeric_limits<size_t>::max();
while (idx == -1 && i > 0) { while (idx == std::numeric_limits<size_t>::max() && i > 0) {
if (array[i] == val) { if (array[i] == val) {
idx = i; idx = i;
break; break;
} }
i--; i--;
} }
if (idx == -1 || i == 0) if (idx == std::numeric_limits<size_t>::max() || i == 0)
return val; return val;
return array[i - 1]; return array[i - 1];
} }

View File

@@ -29,9 +29,9 @@ class MatrixKeypad : public key_provider::KeyProvider, public Component {
void set_columns(std::vector<GPIOPin *> pins) { columns_ = std::move(pins); }; void set_columns(std::vector<GPIOPin *> pins) { columns_ = std::move(pins); };
void set_rows(std::vector<GPIOPin *> pins) { rows_ = std::move(pins); }; void set_rows(std::vector<GPIOPin *> pins) { rows_ = std::move(pins); };
void set_keys(std::string keys) { keys_ = std::move(keys); }; void set_keys(std::string keys) { keys_ = std::move(keys); };
void set_debounce_time(int debounce_time) { debounce_time_ = debounce_time; }; void set_debounce_time(uint32_t debounce_time) { debounce_time_ = debounce_time; };
void set_has_diodes(int has_diodes) { has_diodes_ = has_diodes; }; void set_has_diodes(bool has_diodes) { has_diodes_ = has_diodes; };
void set_has_pulldowns(int has_pulldowns) { has_pulldowns_ = has_pulldowns; }; void set_has_pulldowns(bool has_pulldowns) { has_pulldowns_ = has_pulldowns; };
void register_listener(MatrixKeypadListener *listener); void register_listener(MatrixKeypadListener *listener);
void register_key_trigger(MatrixKeyTrigger *trig); void register_key_trigger(MatrixKeyTrigger *trig);
@@ -40,7 +40,7 @@ class MatrixKeypad : public key_provider::KeyProvider, public Component {
std::vector<GPIOPin *> rows_; std::vector<GPIOPin *> rows_;
std::vector<GPIOPin *> columns_; std::vector<GPIOPin *> columns_;
std::string keys_; std::string keys_;
int debounce_time_ = 0; uint32_t debounce_time_ = 0;
bool has_diodes_{false}; bool has_diodes_{false};
bool has_pulldowns_{false}; bool has_pulldowns_{false};
int pressed_key_ = -1; int pressed_key_ = -1;

View File

@@ -90,7 +90,7 @@ void MAX7219Component::loop() {
} }
if (this->scroll_mode_ == ScrollMode::STOP) { if (this->scroll_mode_ == ScrollMode::STOP) {
if (this->stepsleft_ + get_width_internal() == first_line_size + 1) { if (static_cast<size_t>(this->stepsleft_ + get_width_internal()) == first_line_size + 1) {
if (millis_since_last_scroll < this->scroll_dwell_) { if (millis_since_last_scroll < this->scroll_dwell_) {
ESP_LOGVV(TAG, "Dwell time at end of string in case of stop at end. Step %d, since last scroll %d, dwell %d.", ESP_LOGVV(TAG, "Dwell time at end of string in case of stop at end. Step %d, since last scroll %d, dwell %d.",
this->stepsleft_, millis_since_last_scroll, this->scroll_dwell_); this->stepsleft_, millis_since_last_scroll, this->scroll_dwell_);

View File

@@ -17,6 +17,11 @@ from esphome.coroutine import CoroPriority
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
DEPENDENCIES = ["network"] DEPENDENCIES = ["network"]
# Components that create mDNS services at runtime
# IMPORTANT: If you add a new component here, you must also update the corresponding
# #ifdef blocks in mdns_component.cpp compile_records_() method
COMPONENTS_WITH_MDNS_SERVICES = ("api", "prometheus", "web_server")
mdns_ns = cg.esphome_ns.namespace("mdns") mdns_ns = cg.esphome_ns.namespace("mdns")
MDNSComponent = mdns_ns.class_("MDNSComponent", cg.Component) MDNSComponent = mdns_ns.class_("MDNSComponent", cg.Component)
MDNSTXTRecord = mdns_ns.struct("MDNSTXTRecord") MDNSTXTRecord = mdns_ns.struct("MDNSTXTRecord")
@@ -91,12 +96,20 @@ async def to_code(config):
cg.add_define("USE_MDNS") cg.add_define("USE_MDNS")
var = cg.new_Pvariable(config[CONF_ID]) # Calculate compile-time service count
await cg.register_component(var, config) service_count = sum(
1 for key in COMPONENTS_WITH_MDNS_SERVICES if key in CORE.config
) + len(config[CONF_SERVICES])
if config[CONF_SERVICES]: if config[CONF_SERVICES]:
cg.add_define("USE_MDNS_EXTRA_SERVICES") cg.add_define("USE_MDNS_EXTRA_SERVICES")
# Ensure at least 1 service (fallback service)
cg.add_define("MDNS_SERVICE_COUNT", max(1, service_count))
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
for service in config[CONF_SERVICES]: for service in config[CONF_SERVICES]:
txt = [ txt = [
cg.StructInitializer( cg.StructInitializer(

View File

@@ -74,32 +74,12 @@ MDNS_STATIC_CONST_CHAR(NETWORK_THREAD, "thread");
void MDNSComponent::compile_records_() { void MDNSComponent::compile_records_() {
this->hostname_ = App.get_name(); this->hostname_ = App.get_name();
// Calculate exact capacity needed for services vector // IMPORTANT: The #ifdef blocks below must match COMPONENTS_WITH_MDNS_SERVICES
size_t services_count = 0; // in mdns/__init__.py. If you add a new service here, update both locations.
#ifdef USE_API
if (api::global_api_server != nullptr) {
services_count++;
}
#endif
#ifdef USE_PROMETHEUS
services_count++;
#endif
#ifdef USE_WEBSERVER
services_count++;
#endif
#ifdef USE_MDNS_EXTRA_SERVICES
services_count += this->services_extra_.size();
#endif
// Reserve for fallback service if needed
if (services_count == 0) {
services_count = 1;
}
this->services_.reserve(services_count);
#ifdef USE_API #ifdef USE_API
if (api::global_api_server != nullptr) { if (api::global_api_server != nullptr) {
this->services_.emplace_back(); auto &service = this->services_.emplace_next();
auto &service = this->services_.back();
service.service_type = MDNS_STR(SERVICE_ESPHOMELIB); service.service_type = MDNS_STR(SERVICE_ESPHOMELIB);
service.proto = MDNS_STR(SERVICE_TCP); service.proto = MDNS_STR(SERVICE_TCP);
service.port = api::global_api_server->get_port(); service.port = api::global_api_server->get_port();
@@ -178,30 +158,23 @@ void MDNSComponent::compile_records_() {
#endif // USE_API #endif // USE_API
#ifdef USE_PROMETHEUS #ifdef USE_PROMETHEUS
this->services_.emplace_back(); auto &prom_service = this->services_.emplace_next();
auto &prom_service = this->services_.back();
prom_service.service_type = MDNS_STR(SERVICE_PROMETHEUS); prom_service.service_type = MDNS_STR(SERVICE_PROMETHEUS);
prom_service.proto = MDNS_STR(SERVICE_TCP); prom_service.proto = MDNS_STR(SERVICE_TCP);
prom_service.port = USE_WEBSERVER_PORT; prom_service.port = USE_WEBSERVER_PORT;
#endif #endif
#ifdef USE_WEBSERVER #ifdef USE_WEBSERVER
this->services_.emplace_back(); auto &web_service = this->services_.emplace_next();
auto &web_service = this->services_.back();
web_service.service_type = MDNS_STR(SERVICE_HTTP); web_service.service_type = MDNS_STR(SERVICE_HTTP);
web_service.proto = MDNS_STR(SERVICE_TCP); web_service.proto = MDNS_STR(SERVICE_TCP);
web_service.port = USE_WEBSERVER_PORT; web_service.port = USE_WEBSERVER_PORT;
#endif #endif
#ifdef USE_MDNS_EXTRA_SERVICES
this->services_.insert(this->services_.end(), this->services_extra_.begin(), this->services_extra_.end());
#endif
#if !defined(USE_API) && !defined(USE_PROMETHEUS) && !defined(USE_WEBSERVER) && !defined(USE_MDNS_EXTRA_SERVICES) #if !defined(USE_API) && !defined(USE_PROMETHEUS) && !defined(USE_WEBSERVER) && !defined(USE_MDNS_EXTRA_SERVICES)
// Publish "http" service if not using native API or any other services // Publish "http" service if not using native API or any other services
// This is just to have *some* mDNS service so that .local resolution works // This is just to have *some* mDNS service so that .local resolution works
this->services_.emplace_back(); auto &fallback_service = this->services_.emplace_next();
auto &fallback_service = this->services_.back();
fallback_service.service_type = "_http"; fallback_service.service_type = "_http";
fallback_service.proto = "_tcp"; fallback_service.proto = "_tcp";
fallback_service.port = USE_WEBSERVER_PORT; fallback_service.port = USE_WEBSERVER_PORT;
@@ -214,7 +187,7 @@ void MDNSComponent::dump_config() {
"mDNS:\n" "mDNS:\n"
" Hostname: %s", " Hostname: %s",
this->hostname_.c_str()); this->hostname_.c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
ESP_LOGV(TAG, " Services:"); ESP_LOGV(TAG, " Services:");
for (const auto &service : this->services_) { for (const auto &service : this->services_) {
ESP_LOGV(TAG, " - %s, %s, %d", service.service_type.c_str(), service.proto.c_str(), ESP_LOGV(TAG, " - %s, %s, %d", service.service_type.c_str(), service.proto.c_str(),
@@ -227,8 +200,6 @@ void MDNSComponent::dump_config() {
#endif #endif
} }
std::vector<MDNSService> MDNSComponent::get_services() { return this->services_; }
} // namespace mdns } // namespace mdns
} // namespace esphome } // namespace esphome
#endif #endif

View File

@@ -2,13 +2,16 @@
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#ifdef USE_MDNS #ifdef USE_MDNS
#include <string> #include <string>
#include <vector>
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/helpers.h"
namespace esphome { namespace esphome {
namespace mdns { namespace mdns {
// Service count is calculated at compile time by Python codegen
// MDNS_SERVICE_COUNT will always be defined
struct MDNSTXTRecord { struct MDNSTXTRecord {
std::string key; std::string key;
TemplatableValue<std::string> value; TemplatableValue<std::string> value;
@@ -36,18 +39,15 @@ class MDNSComponent : public Component {
float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; } float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; }
#ifdef USE_MDNS_EXTRA_SERVICES #ifdef USE_MDNS_EXTRA_SERVICES
void add_extra_service(MDNSService service) { services_extra_.push_back(std::move(service)); } void add_extra_service(MDNSService service) { this->services_.emplace_next() = std::move(service); }
#endif #endif
std::vector<MDNSService> get_services(); const StaticVector<MDNSService, MDNS_SERVICE_COUNT> &get_services() const { return this->services_; }
void on_shutdown() override; void on_shutdown() override;
protected: protected:
#ifdef USE_MDNS_EXTRA_SERVICES StaticVector<MDNSService, MDNS_SERVICE_COUNT> services_{};
std::vector<MDNSService> services_extra_{};
#endif
std::vector<MDNSService> services_{};
std::string hostname_; std::string hostname_;
void compile_records_(); void compile_records_();
}; };

View File

@@ -343,11 +343,7 @@ class DriverChip:
) )
offset_height = native_height - height - offset_height offset_height = native_height - height - offset_height
# Swap default dimensions if swap_xy is set, or if rotation is 90/270 and we are not using a buffer # Swap default dimensions if swap_xy is set, or if rotation is 90/270 and we are not using a buffer
rotated = not requires_buffer(config) and config.get(CONF_ROTATION, 0) in ( if transform.get(CONF_SWAP_XY) is True:
90,
270,
)
if transform.get(CONF_SWAP_XY) is True or rotated:
width, height = height, width width, height = height, width
offset_height, offset_width = offset_width, offset_height offset_height, offset_width = offset_width, offset_height
return width, height, offset_width, offset_height return width, height, offset_width, offset_height

View File

@@ -380,25 +380,41 @@ def get_instance(config):
bus_type = BusTypes[bus_type] bus_type = BusTypes[bus_type]
buffer_type = cg.uint8 if color_depth == 8 else cg.uint16 buffer_type = cg.uint8 if color_depth == 8 else cg.uint16
frac = denominator(config) frac = denominator(config)
rotation = DISPLAY_ROTATIONS[ rotation = (
0 if model.rotation_as_transform(config) else config.get(CONF_ROTATION, 0) 0 if model.rotation_as_transform(config) else config.get(CONF_ROTATION, 0)
] )
templateargs = [ templateargs = [
buffer_type, buffer_type,
bufferpixels, bufferpixels,
config[CONF_BYTE_ORDER] == "big_endian", config[CONF_BYTE_ORDER] == "big_endian",
display_pixel_mode, display_pixel_mode,
bus_type, bus_type,
width,
height,
offset_width,
offset_height,
] ]
# If a buffer is required, use MipiSpiBuffer, otherwise use MipiSpi # If a buffer is required, use MipiSpiBuffer, otherwise use MipiSpi
if requires_buffer(config): if requires_buffer(config):
templateargs.append(rotation) templateargs.extend(
templateargs.append(frac) [
width,
height,
offset_width,
offset_height,
DISPLAY_ROTATIONS[rotation],
frac,
]
)
return MipiSpiBuffer, templateargs return MipiSpiBuffer, templateargs
# Swap height and width if the display is rotated 90 or 270 degrees in software
if rotation in (90, 270):
width, height = height, width
offset_width, offset_height = offset_height, offset_width
templateargs.extend(
[
width,
height,
offset_width,
offset_height,
]
)
return MipiSpi, templateargs return MipiSpi, templateargs

View File

@@ -340,7 +340,7 @@ class MipiSpi : public display::Display,
this->write_cmd_addr_data(0, 0, 0, 0, ptr, w * h, 8); this->write_cmd_addr_data(0, 0, 0, 0, ptr, w * h, 8);
} }
} else { } else {
for (size_t y = 0; y != h; y++) { for (size_t y = 0; y != static_cast<size_t>(h); y++) {
if constexpr (BUS_TYPE == BUS_TYPE_SINGLE || BUS_TYPE == BUS_TYPE_SINGLE_16) { if constexpr (BUS_TYPE == BUS_TYPE_SINGLE || BUS_TYPE == BUS_TYPE_SINGLE_16) {
this->write_array(ptr, w); this->write_array(ptr, w);
} else if constexpr (BUS_TYPE == BUS_TYPE_QUAD) { } else if constexpr (BUS_TYPE == BUS_TYPE_QUAD) {
@@ -372,8 +372,8 @@ class MipiSpi : public display::Display,
uint8_t dbuffer[DISPLAYPIXEL * 48]; uint8_t dbuffer[DISPLAYPIXEL * 48];
uint8_t *dptr = dbuffer; uint8_t *dptr = dbuffer;
auto stride = x_offset + w + x_pad; // stride in pixels auto stride = x_offset + w + x_pad; // stride in pixels
for (size_t y = 0; y != h; y++) { for (size_t y = 0; y != static_cast<size_t>(h); y++) {
for (size_t x = 0; x != w; x++) { for (size_t x = 0; x != static_cast<size_t>(w); x++) {
auto color_val = ptr[y * stride + x]; auto color_val = ptr[y * stride + x];
if constexpr (DISPLAYPIXEL == PIXEL_MODE_18 && BUFFERPIXEL == PIXEL_MODE_16) { if constexpr (DISPLAYPIXEL == PIXEL_MODE_18 && BUFFERPIXEL == PIXEL_MODE_16) {
// 16 to 18 bit conversion // 16 to 18 bit conversion

View File

@@ -572,7 +572,7 @@ void MixerSpeaker::audio_mixer_task(void *params) {
} }
} else { } else {
// Determine how many frames to mix // Determine how many frames to mix
for (int i = 0; i < transfer_buffers_with_data.size(); ++i) { for (size_t i = 0; i < transfer_buffers_with_data.size(); ++i) {
const uint32_t frames_available_in_buffer = const uint32_t frames_available_in_buffer =
speakers_with_data[i]->get_audio_stream_info().bytes_to_frames(transfer_buffers_with_data[i]->available()); speakers_with_data[i]->get_audio_stream_info().bytes_to_frames(transfer_buffers_with_data[i]->available());
frames_to_mix = std::min(frames_to_mix, frames_available_in_buffer); frames_to_mix = std::min(frames_to_mix, frames_available_in_buffer);
@@ -581,7 +581,7 @@ void MixerSpeaker::audio_mixer_task(void *params) {
audio::AudioStreamInfo primary_stream_info = speakers_with_data[0]->get_audio_stream_info(); audio::AudioStreamInfo primary_stream_info = speakers_with_data[0]->get_audio_stream_info();
// Mix two streams together // Mix two streams together
for (int i = 1; i < transfer_buffers_with_data.size(); ++i) { for (size_t i = 1; i < transfer_buffers_with_data.size(); ++i) {
mix_audio_samples(primary_buffer, primary_stream_info, mix_audio_samples(primary_buffer, primary_stream_info,
reinterpret_cast<int16_t *>(transfer_buffers_with_data[i]->get_buffer_start()), reinterpret_cast<int16_t *>(transfer_buffers_with_data[i]->get_buffer_start()),
speakers_with_data[i]->get_audio_stream_info(), speakers_with_data[i]->get_audio_stream_info(),
@@ -596,7 +596,7 @@ void MixerSpeaker::audio_mixer_task(void *params) {
} }
// Update source transfer buffer lengths and add new audio durations to the source speaker pending playbacks // Update source transfer buffer lengths and add new audio durations to the source speaker pending playbacks
for (int i = 0; i < transfer_buffers_with_data.size(); ++i) { for (size_t i = 0; i < transfer_buffers_with_data.size(); ++i) {
transfer_buffers_with_data[i]->decrease_buffer_length( transfer_buffers_with_data[i]->decrease_buffer_length(
speakers_with_data[i]->get_audio_stream_info().frames_to_bytes(frames_to_mix)); speakers_with_data[i]->get_audio_stream_info().frames_to_bytes(frames_to_mix));
speakers_with_data[i]->pending_playback_frames_ += frames_to_mix; speakers_with_data[i]->pending_playback_frames_ += frames_to_mix;

View File

@@ -11,47 +11,49 @@ namespace mpr121 {
static const char *const TAG = "mpr121"; static const char *const TAG = "mpr121";
void MPR121Component::setup() { void MPR121Component::setup() {
this->disable_loop();
// soft reset device // soft reset device
this->write_byte(MPR121_SOFTRESET, 0x63); this->write_byte(MPR121_SOFTRESET, 0x63);
delay(100); // NOLINT this->set_timeout(100, [this]() {
if (!this->write_byte(MPR121_ECR, 0x0)) { if (!this->write_byte(MPR121_ECR, 0x0)) {
this->error_code_ = COMMUNICATION_FAILED; this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed(); this->mark_failed();
return; return;
} }
// set touch sensitivity for all 12 channels
for (auto *channel : this->channels_) {
channel->setup();
}
this->write_byte(MPR121_MHDR, 0x01);
this->write_byte(MPR121_NHDR, 0x01);
this->write_byte(MPR121_NCLR, 0x0E);
this->write_byte(MPR121_FDLR, 0x00);
// set touch sensitivity for all 12 channels this->write_byte(MPR121_MHDF, 0x01);
for (auto *channel : this->channels_) { this->write_byte(MPR121_NHDF, 0x05);
channel->setup(); this->write_byte(MPR121_NCLF, 0x01);
} this->write_byte(MPR121_FDLF, 0x00);
this->write_byte(MPR121_MHDR, 0x01);
this->write_byte(MPR121_NHDR, 0x01);
this->write_byte(MPR121_NCLR, 0x0E);
this->write_byte(MPR121_FDLR, 0x00);
this->write_byte(MPR121_MHDF, 0x01); this->write_byte(MPR121_NHDT, 0x00);
this->write_byte(MPR121_NHDF, 0x05); this->write_byte(MPR121_NCLT, 0x00);
this->write_byte(MPR121_NCLF, 0x01); this->write_byte(MPR121_FDLT, 0x00);
this->write_byte(MPR121_FDLF, 0x00);
this->write_byte(MPR121_NHDT, 0x00); this->write_byte(MPR121_DEBOUNCE, 0);
this->write_byte(MPR121_NCLT, 0x00); // default, 16uA charge current
this->write_byte(MPR121_FDLT, 0x00); this->write_byte(MPR121_CONFIG1, 0x10);
// 0.5uS encoding, 1ms period
this->write_byte(MPR121_CONFIG2, 0x20);
this->write_byte(MPR121_DEBOUNCE, 0); // Write the Electrode Configuration Register
// default, 16uA charge current // * Highest 2 bits is "Calibration Lock", which we set to a value corresponding to 5 bits.
this->write_byte(MPR121_CONFIG1, 0x10); // * The 2 bits below is "Proximity Enable" and are left at 0.
// 0.5uS encoding, 1ms period // * The 4 least significant bits control how many electrodes are enabled. Electrodes are enabled
this->write_byte(MPR121_CONFIG2, 0x20); // as a range, starting at 0 up to the highest channel index used.
this->write_byte(MPR121_ECR, 0x80 | (this->max_touch_channel_ + 1));
// Write the Electrode Configuration Register this->flush_gpio_();
// * Highest 2 bits is "Calibration Lock", which we set to a value corresponding to 5 bits. this->enable_loop();
// * The 2 bits below is "Proximity Enable" and are left at 0. });
// * The 4 least significant bits control how many electrodes are enabled. Electrodes are enabled
// as a range, starting at 0 up to the highest channel index used.
this->write_byte(MPR121_ECR, 0x80 | (this->max_touch_channel_ + 1));
this->flush_gpio_();
} }
void MPR121Component::set_touch_debounce(uint8_t debounce) { void MPR121Component::set_touch_debounce(uint8_t debounce) {
@@ -73,9 +75,6 @@ void MPR121Component::dump_config() {
case COMMUNICATION_FAILED: case COMMUNICATION_FAILED:
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
break; break;
case WRONG_CHIP_STATE:
ESP_LOGE(TAG, "MPR121 has wrong default value for CONFIG2?");
break;
case NONE: case NONE:
default: default:
break; break;

View File

@@ -88,7 +88,6 @@ class MPR121Component : public Component, public i2c::I2CDevice {
enum ErrorCode { enum ErrorCode {
NONE = 0, NONE = 0,
COMMUNICATION_FAILED, COMMUNICATION_FAILED,
WRONG_CHIP_STATE,
} error_code_{NONE}; } error_code_{NONE};
bool flush_gpio_(); bool flush_gpio_();

View File

@@ -218,7 +218,7 @@ void NAU7802Sensor::dump_config() {
void NAU7802Sensor::write_value_(uint8_t start_reg, size_t size, int32_t value) { void NAU7802Sensor::write_value_(uint8_t start_reg, size_t size, int32_t value) {
uint8_t data[4]; uint8_t data[4];
for (int i = 0; i < size; i++) { for (size_t i = 0; i < size; i++) {
data[i] = 0xFF & (value >> (size - 1 - i) * 8); data[i] = 0xFF & (value >> (size - 1 - i) * 8);
} }
this->write_register(start_reg, data, size); this->write_register(start_reg, data, size);
@@ -228,7 +228,7 @@ int32_t NAU7802Sensor::read_value_(uint8_t start_reg, size_t size) {
uint8_t data[4]; uint8_t data[4];
this->read_register(start_reg, data, size); this->read_register(start_reg, data, size);
int32_t result = 0; int32_t result = 0;
for (int i = 0; i < size; i++) { for (size_t i = 0; i < size; i++) {
result |= data[i] << (size - 1 - i) * 8; result |= data[i] << (size - 1 - i) * 8;
} }
// extend sign bit // extend sign bit

View File

@@ -77,7 +77,7 @@ bool Nextion::check_connect_() {
this->recv_ret_string_(response, 0, false); this->recv_ret_string_(response, 0, false);
if (!response.empty() && response[0] == 0x1A) { if (!response.empty() && response[0] == 0x1A) {
// Swallow invalid variable name responses that may be caused by the above commands // Swallow invalid variable name responses that may be caused by the above commands
ESP_LOGD(TAG, "0x1A error ignored (setup)"); ESP_LOGV(TAG, "0x1A error ignored (setup)");
return false; return false;
} }
if (response.empty() || response.find("comok") == std::string::npos) { if (response.empty() || response.find("comok") == std::string::npos) {
@@ -334,7 +334,7 @@ void Nextion::loop() {
this->started_ms_ = App.get_loop_component_start_time(); this->started_ms_ = App.get_loop_component_start_time();
if (this->started_ms_ + this->startup_override_ms_ < App.get_loop_component_start_time()) { if (this->started_ms_ + this->startup_override_ms_ < App.get_loop_component_start_time()) {
ESP_LOGD(TAG, "Manual ready set"); ESP_LOGV(TAG, "Manual ready set");
this->connection_state_.nextion_reports_is_setup_ = true; this->connection_state_.nextion_reports_is_setup_ = true;
} }
} }
@@ -544,7 +544,7 @@ void Nextion::process_nextion_commands_() {
uint8_t page_id = to_process[0]; uint8_t page_id = to_process[0];
uint8_t component_id = to_process[1]; uint8_t component_id = to_process[1];
uint8_t touch_event = to_process[2]; // 0 -> release, 1 -> press uint8_t touch_event = to_process[2]; // 0 -> release, 1 -> press
ESP_LOGD(TAG, "Touch %s: page %u comp %u", touch_event ? "PRESS" : "RELEASE", page_id, component_id); ESP_LOGV(TAG, "Touch %s: page %u comp %u", touch_event ? "PRESS" : "RELEASE", page_id, component_id);
for (auto *touch : this->touch_) { for (auto *touch : this->touch_) {
touch->process_touch(page_id, component_id, touch_event != 0); touch->process_touch(page_id, component_id, touch_event != 0);
} }
@@ -559,7 +559,7 @@ void Nextion::process_nextion_commands_() {
} }
uint8_t page_id = to_process[0]; uint8_t page_id = to_process[0];
ESP_LOGD(TAG, "New page: %u", page_id); ESP_LOGV(TAG, "New page: %u", page_id);
this->page_callback_.call(page_id); this->page_callback_.call(page_id);
break; break;
} }
@@ -577,7 +577,7 @@ void Nextion::process_nextion_commands_() {
const uint16_t x = (uint16_t(to_process[0]) << 8) | to_process[1]; const uint16_t x = (uint16_t(to_process[0]) << 8) | to_process[1];
const uint16_t y = (uint16_t(to_process[2]) << 8) | to_process[3]; const uint16_t y = (uint16_t(to_process[2]) << 8) | to_process[3];
const uint8_t touch_event = to_process[4]; // 0 -> release, 1 -> press const uint8_t touch_event = to_process[4]; // 0 -> release, 1 -> press
ESP_LOGD(TAG, "Touch %s at %u,%u", touch_event ? "PRESS" : "RELEASE", x, y); ESP_LOGV(TAG, "Touch %s at %u,%u", touch_event ? "PRESS" : "RELEASE", x, y);
break; break;
} }
@@ -676,7 +676,7 @@ void Nextion::process_nextion_commands_() {
} }
case 0x88: // system successful start up case 0x88: // system successful start up
{ {
ESP_LOGD(TAG, "System start: %zu", to_process_length); ESP_LOGV(TAG, "System start: %zu", to_process_length);
this->connection_state_.nextion_reports_is_setup_ = true; this->connection_state_.nextion_reports_is_setup_ = true;
break; break;
} }
@@ -922,7 +922,7 @@ void Nextion::set_nextion_sensor_state(NextionQueueType queue_type, const std::s
} }
void Nextion::set_nextion_text_state(const std::string &name, const std::string &state) { void Nextion::set_nextion_text_state(const std::string &name, const std::string &state) {
ESP_LOGD(TAG, "State: %s='%s'", name.c_str(), state.c_str()); ESP_LOGV(TAG, "State: %s='%s'", name.c_str(), state.c_str());
for (auto *sensor : this->textsensortype_) { for (auto *sensor : this->textsensortype_) {
if (name == sensor->get_variable_name()) { if (name == sensor->get_variable_name()) {
@@ -933,7 +933,7 @@ void Nextion::set_nextion_text_state(const std::string &name, const std::string
} }
void Nextion::all_components_send_state_(bool force_update) { void Nextion::all_components_send_state_(bool force_update) {
ESP_LOGD(TAG, "Send states"); ESP_LOGV(TAG, "Send states");
for (auto *binarysensortype : this->binarysensortype_) { for (auto *binarysensortype : this->binarysensortype_) {
if (force_update || binarysensortype->get_needs_to_send_update()) if (force_update || binarysensortype->get_needs_to_send_update())
binarysensortype->send_state_to_nextion(); binarysensortype->send_state_to_nextion();

View File

@@ -7,6 +7,17 @@ namespace number {
static const char *const TAG = "number"; static const char *const TAG = "number";
// Helper functions to reduce code size for logging
void NumberCall::log_perform_warning_(const LogString *message) {
ESP_LOGW(TAG, "'%s': %s", this->parent_->get_name().c_str(), LOG_STR_ARG(message));
}
void NumberCall::log_perform_warning_value_range_(const LogString *comparison, const LogString *limit_type, float val,
float limit) {
ESP_LOGW(TAG, "'%s': %f %s %s %f", this->parent_->get_name().c_str(), val, LOG_STR_ARG(comparison),
LOG_STR_ARG(limit_type), limit);
}
NumberCall &NumberCall::set_value(float value) { return this->with_operation(NUMBER_OP_SET).with_value(value); } NumberCall &NumberCall::set_value(float value) { return this->with_operation(NUMBER_OP_SET).with_value(value); }
NumberCall &NumberCall::number_increment(bool cycle) { NumberCall &NumberCall::number_increment(bool cycle) {
@@ -42,7 +53,7 @@ void NumberCall::perform() {
const auto &traits = parent->traits; const auto &traits = parent->traits;
if (this->operation_ == NUMBER_OP_NONE) { if (this->operation_ == NUMBER_OP_NONE) {
ESP_LOGW(TAG, "'%s' - NumberCall performed without selecting an operation", name); this->log_perform_warning_(LOG_STR("No operation"));
return; return;
} }
@@ -51,28 +62,28 @@ void NumberCall::perform() {
float max_value = traits.get_max_value(); float max_value = traits.get_max_value();
if (this->operation_ == NUMBER_OP_SET) { if (this->operation_ == NUMBER_OP_SET) {
ESP_LOGD(TAG, "'%s' - Setting number value", name); ESP_LOGD(TAG, "'%s': Setting value", name);
if (!this->value_.has_value() || std::isnan(*this->value_)) { if (!this->value_.has_value() || std::isnan(*this->value_)) {
ESP_LOGW(TAG, "'%s' - No value set for NumberCall", name); this->log_perform_warning_(LOG_STR("No value"));
return; return;
} }
target_value = this->value_.value(); target_value = this->value_.value();
} else if (this->operation_ == NUMBER_OP_TO_MIN) { } else if (this->operation_ == NUMBER_OP_TO_MIN) {
if (std::isnan(min_value)) { if (std::isnan(min_value)) {
ESP_LOGW(TAG, "'%s' - Can't set to min value through NumberCall: no min_value defined", name); this->log_perform_warning_(LOG_STR("min undefined"));
} else { } else {
target_value = min_value; target_value = min_value;
} }
} else if (this->operation_ == NUMBER_OP_TO_MAX) { } else if (this->operation_ == NUMBER_OP_TO_MAX) {
if (std::isnan(max_value)) { if (std::isnan(max_value)) {
ESP_LOGW(TAG, "'%s' - Can't set to max value through NumberCall: no max_value defined", name); this->log_perform_warning_(LOG_STR("max undefined"));
} else { } else {
target_value = max_value; target_value = max_value;
} }
} else if (this->operation_ == NUMBER_OP_INCREMENT) { } else if (this->operation_ == NUMBER_OP_INCREMENT) {
ESP_LOGD(TAG, "'%s' - Increment number, with%s cycling", name, this->cycle_ ? "" : "out"); ESP_LOGD(TAG, "'%s': Increment with%s cycling", name, this->cycle_ ? "" : "out");
if (!parent->has_state()) { if (!parent->has_state()) {
ESP_LOGW(TAG, "'%s' - Can't increment number through NumberCall: no active state to modify", name); this->log_perform_warning_(LOG_STR("Can't increment, no state"));
return; return;
} }
auto step = traits.get_step(); auto step = traits.get_step();
@@ -85,9 +96,9 @@ void NumberCall::perform() {
} }
} }
} else if (this->operation_ == NUMBER_OP_DECREMENT) { } else if (this->operation_ == NUMBER_OP_DECREMENT) {
ESP_LOGD(TAG, "'%s' - Decrement number, with%s cycling", name, this->cycle_ ? "" : "out"); ESP_LOGD(TAG, "'%s': Decrement with%s cycling", name, this->cycle_ ? "" : "out");
if (!parent->has_state()) { if (!parent->has_state()) {
ESP_LOGW(TAG, "'%s' - Can't decrement number through NumberCall: no active state to modify", name); this->log_perform_warning_(LOG_STR("Can't decrement, no state"));
return; return;
} }
auto step = traits.get_step(); auto step = traits.get_step();
@@ -102,15 +113,15 @@ void NumberCall::perform() {
} }
if (target_value < min_value) { if (target_value < min_value) {
ESP_LOGW(TAG, "'%s' - Value %f must not be less than minimum %f", name, target_value, min_value); this->log_perform_warning_value_range_(LOG_STR("<"), LOG_STR("min"), target_value, min_value);
return; return;
} }
if (target_value > max_value) { if (target_value > max_value) {
ESP_LOGW(TAG, "'%s' - Value %f must not be greater than maximum %f", name, target_value, max_value); this->log_perform_warning_value_range_(LOG_STR(">"), LOG_STR("max"), target_value, max_value);
return; return;
} }
ESP_LOGD(TAG, " New number value: %f", target_value); ESP_LOGD(TAG, " New value: %f", target_value);
this->parent_->control(target_value); this->parent_->control(target_value);
} }

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "number_traits.h" #include "number_traits.h"
namespace esphome { namespace esphome {
@@ -33,6 +34,10 @@ class NumberCall {
NumberCall &with_cycle(bool cycle); NumberCall &with_cycle(bool cycle);
protected: protected:
void log_perform_warning_(const LogString *message);
void log_perform_warning_value_range_(const LogString *comparison, const LogString *limit_type, float val,
float limit);
Number *const parent_; Number *const parent_;
NumberOperation operation_{NUMBER_OP_NONE}; NumberOperation operation_{NUMBER_OP_NONE};
optional<float> value_; optional<float> value_;

View File

@@ -117,7 +117,8 @@ int HOT BmpDecoder::decode(uint8_t *buffer, size_t size) {
this->paint_index_++; this->paint_index_++;
this->current_index_ += 3; this->current_index_ += 3;
index += 3; index += 3;
if (x == this->width_ - 1 && this->padding_bytes_ > 0) { size_t last_col = static_cast<size_t>(this->width_) - 1;
if (x == last_col && this->padding_bytes_ > 0) {
index += this->padding_bytes_; index += this->padding_bytes_;
this->current_index_ += this->padding_bytes_; this->current_index_ += this->padding_bytes_;
} }

View File

@@ -25,8 +25,10 @@ static int draw_callback(JPEGDRAW *jpeg) {
// to avoid crashing. // to avoid crashing.
App.feed_wdt(); App.feed_wdt();
size_t position = 0; size_t position = 0;
for (size_t y = 0; y < jpeg->iHeight; y++) { size_t height = static_cast<size_t>(jpeg->iHeight);
for (size_t x = 0; x < jpeg->iWidth; x++) { size_t width = static_cast<size_t>(jpeg->iWidth);
for (size_t y = 0; y < height; y++) {
for (size_t x = 0; x < width; x++) {
auto rg = decode_value(jpeg->pPixels[position++]); auto rg = decode_value(jpeg->pPixels[position++]);
auto ba = decode_value(jpeg->pPixels[position++]); auto ba = decode_value(jpeg->pPixels[position++]);
Color color(rg[1], rg[0], ba[1], ba[0]); Color color(rg[1], rg[0], ba[1], ba[0]);

View File

@@ -143,11 +143,10 @@ void OpenThreadSrpComponent::setup() {
return; return;
} }
// Copy the mdns services to our local instance so that the c_str pointers remain valid for the lifetime of this // Get mdns services and copy their data (strings are copied with strdup below)
// component const auto &mdns_services = this->mdns_->get_services();
this->mdns_services_ = this->mdns_->get_services(); ESP_LOGD(TAG, "Setting up SRP services. count = %d\n", mdns_services.size());
ESP_LOGD(TAG, "Setting up SRP services. count = %d\n", this->mdns_services_.size()); for (const auto &service : mdns_services) {
for (const auto &service : this->mdns_services_) {
otSrpClientBuffersServiceEntry *entry = otSrpClientBuffersAllocateService(instance); otSrpClientBuffersServiceEntry *entry = otSrpClientBuffersAllocateService(instance);
if (!entry) { if (!entry) {
ESP_LOGW(TAG, "Failed to allocate service entry"); ESP_LOGW(TAG, "Failed to allocate service entry");

View File

@@ -57,7 +57,6 @@ class OpenThreadSrpComponent : public Component {
protected: protected:
esphome::mdns::MDNSComponent *mdns_{nullptr}; esphome::mdns::MDNSComponent *mdns_{nullptr};
std::vector<esphome::mdns::MDNSService> mdns_services_;
std::vector<std::unique_ptr<uint8_t[]>> memory_pool_; std::vector<std::unique_ptr<uint8_t[]>> memory_pool_;
void *pool_alloc_(size_t size); void *pool_alloc_(size_t size);
}; };

View File

@@ -104,7 +104,7 @@ float PIDController::weighted_average_(std::deque<float> &list, float new_value,
list.push_front(new_value); list.push_front(new_value);
// keep only 'samples' readings, by popping off the back of the list // keep only 'samples' readings, by popping off the back of the list
while (list.size() > samples) while (samples > 0 && list.size() > static_cast<size_t>(samples))
list.pop_back(); list.pop_back();
// calculate and return the average of all values in the list // calculate and return the average of all values in the list

View File

@@ -110,21 +110,21 @@ std::string PrometheusHandler::relabel_name_(EntityBase *obj) {
void PrometheusHandler::add_area_label_(AsyncResponseStream *stream, std::string &area) { void PrometheusHandler::add_area_label_(AsyncResponseStream *stream, std::string &area) {
if (!area.empty()) { if (!area.empty()) {
stream->print(F("\",area=\"")); stream->print(ESPHOME_F("\",area=\""));
stream->print(area.c_str()); stream->print(area.c_str());
} }
} }
void PrometheusHandler::add_node_label_(AsyncResponseStream *stream, std::string &node) { void PrometheusHandler::add_node_label_(AsyncResponseStream *stream, std::string &node) {
if (!node.empty()) { if (!node.empty()) {
stream->print(F("\",node=\"")); stream->print(ESPHOME_F("\",node=\""));
stream->print(node.c_str()); stream->print(node.c_str());
} }
} }
void PrometheusHandler::add_friendly_name_label_(AsyncResponseStream *stream, std::string &friendly_name) { void PrometheusHandler::add_friendly_name_label_(AsyncResponseStream *stream, std::string &friendly_name) {
if (!friendly_name.empty()) { if (!friendly_name.empty()) {
stream->print(F("\",friendly_name=\"")); stream->print(ESPHOME_F("\",friendly_name=\""));
stream->print(friendly_name.c_str()); stream->print(friendly_name.c_str());
} }
} }
@@ -132,8 +132,8 @@ void PrometheusHandler::add_friendly_name_label_(AsyncResponseStream *stream, st
// Type-specific implementation // Type-specific implementation
#ifdef USE_SENSOR #ifdef USE_SENSOR
void PrometheusHandler::sensor_type_(AsyncResponseStream *stream) { void PrometheusHandler::sensor_type_(AsyncResponseStream *stream) {
stream->print(F("#TYPE esphome_sensor_value gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_sensor_value gauge\n"));
stream->print(F("#TYPE esphome_sensor_failed gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_sensor_failed gauge\n"));
} }
void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor *obj, std::string &area, void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor *obj, std::string &area,
std::string &node, std::string &friendly_name) { std::string &node, std::string &friendly_name) {
@@ -141,37 +141,37 @@ void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor
return; return;
if (!std::isnan(obj->state)) { if (!std::isnan(obj->state)) {
// We have a valid value, output this value // We have a valid value, output this value
stream->print(F("esphome_sensor_failed{id=\"")); stream->print(ESPHOME_F("esphome_sensor_failed{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} 0\n")); stream->print(ESPHOME_F("\"} 0\n"));
// Data itself // Data itself
stream->print(F("esphome_sensor_value{id=\"")); stream->print(ESPHOME_F("esphome_sensor_value{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\",unit=\"")); stream->print(ESPHOME_F("\",unit=\""));
stream->print(obj->get_unit_of_measurement().c_str()); stream->print(obj->get_unit_of_measurement().c_str());
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
stream->print(value_accuracy_to_string(obj->state, obj->get_accuracy_decimals()).c_str()); stream->print(value_accuracy_to_string(obj->state, obj->get_accuracy_decimals()).c_str());
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
} else { } else {
// Invalid state // Invalid state
stream->print(F("esphome_sensor_failed{id=\"")); stream->print(ESPHOME_F("esphome_sensor_failed{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} 1\n")); stream->print(ESPHOME_F("\"} 1\n"));
} }
} }
#endif #endif
@@ -179,8 +179,8 @@ void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor
// Type-specific implementation // Type-specific implementation
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
void PrometheusHandler::binary_sensor_type_(AsyncResponseStream *stream) { void PrometheusHandler::binary_sensor_type_(AsyncResponseStream *stream) {
stream->print(F("#TYPE esphome_binary_sensor_value gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_binary_sensor_value gauge\n"));
stream->print(F("#TYPE esphome_binary_sensor_failed gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_binary_sensor_failed gauge\n"));
} }
void PrometheusHandler::binary_sensor_row_(AsyncResponseStream *stream, binary_sensor::BinarySensor *obj, void PrometheusHandler::binary_sensor_row_(AsyncResponseStream *stream, binary_sensor::BinarySensor *obj,
std::string &area, std::string &node, std::string &friendly_name) { std::string &area, std::string &node, std::string &friendly_name) {
@@ -188,204 +188,204 @@ void PrometheusHandler::binary_sensor_row_(AsyncResponseStream *stream, binary_s
return; return;
if (obj->has_state()) { if (obj->has_state()) {
// We have a valid value, output this value // We have a valid value, output this value
stream->print(F("esphome_binary_sensor_failed{id=\"")); stream->print(ESPHOME_F("esphome_binary_sensor_failed{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} 0\n")); stream->print(ESPHOME_F("\"} 0\n"));
// Data itself // Data itself
stream->print(F("esphome_binary_sensor_value{id=\"")); stream->print(ESPHOME_F("esphome_binary_sensor_value{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
stream->print(obj->state); stream->print(obj->state);
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
} else { } else {
// Invalid state // Invalid state
stream->print(F("esphome_binary_sensor_failed{id=\"")); stream->print(ESPHOME_F("esphome_binary_sensor_failed{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} 1\n")); stream->print(ESPHOME_F("\"} 1\n"));
} }
} }
#endif #endif
#ifdef USE_FAN #ifdef USE_FAN
void PrometheusHandler::fan_type_(AsyncResponseStream *stream) { void PrometheusHandler::fan_type_(AsyncResponseStream *stream) {
stream->print(F("#TYPE esphome_fan_value gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_fan_value gauge\n"));
stream->print(F("#TYPE esphome_fan_failed gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_fan_failed gauge\n"));
stream->print(F("#TYPE esphome_fan_speed gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_fan_speed gauge\n"));
stream->print(F("#TYPE esphome_fan_oscillation gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_fan_oscillation gauge\n"));
} }
void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::Fan *obj, std::string &area, std::string &node, void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::Fan *obj, std::string &area, std::string &node,
std::string &friendly_name) { std::string &friendly_name) {
if (obj->is_internal() && !this->include_internal_) if (obj->is_internal() && !this->include_internal_)
return; return;
stream->print(F("esphome_fan_failed{id=\"")); stream->print(ESPHOME_F("esphome_fan_failed{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} 0\n")); stream->print(ESPHOME_F("\"} 0\n"));
// Data itself // Data itself
stream->print(F("esphome_fan_value{id=\"")); stream->print(ESPHOME_F("esphome_fan_value{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
stream->print(obj->state); stream->print(obj->state);
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
// Speed if available // Speed if available
if (obj->get_traits().supports_speed()) { if (obj->get_traits().supports_speed()) {
stream->print(F("esphome_fan_speed{id=\"")); stream->print(ESPHOME_F("esphome_fan_speed{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
stream->print(obj->speed); stream->print(obj->speed);
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
} }
// Oscillation if available // Oscillation if available
if (obj->get_traits().supports_oscillation()) { if (obj->get_traits().supports_oscillation()) {
stream->print(F("esphome_fan_oscillation{id=\"")); stream->print(ESPHOME_F("esphome_fan_oscillation{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
stream->print(obj->oscillating); stream->print(obj->oscillating);
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
} }
} }
#endif #endif
#ifdef USE_LIGHT #ifdef USE_LIGHT
void PrometheusHandler::light_type_(AsyncResponseStream *stream) { void PrometheusHandler::light_type_(AsyncResponseStream *stream) {
stream->print(F("#TYPE esphome_light_state gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_light_state gauge\n"));
stream->print(F("#TYPE esphome_light_color gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_light_color gauge\n"));
stream->print(F("#TYPE esphome_light_effect_active gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_light_effect_active gauge\n"));
} }
void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightState *obj, std::string &area, void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightState *obj, std::string &area,
std::string &node, std::string &friendly_name) { std::string &node, std::string &friendly_name) {
if (obj->is_internal() && !this->include_internal_) if (obj->is_internal() && !this->include_internal_)
return; return;
// State // State
stream->print(F("esphome_light_state{id=\"")); stream->print(ESPHOME_F("esphome_light_state{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
stream->print(obj->remote_values.is_on()); stream->print(obj->remote_values.is_on());
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
// Brightness and RGBW // Brightness and RGBW
light::LightColorValues color = obj->current_values; light::LightColorValues color = obj->current_values;
float brightness, r, g, b, w; float brightness, r, g, b, w;
color.as_brightness(&brightness); color.as_brightness(&brightness);
color.as_rgbw(&r, &g, &b, &w); color.as_rgbw(&r, &g, &b, &w);
stream->print(F("esphome_light_color{id=\"")); stream->print(ESPHOME_F("esphome_light_color{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\",channel=\"brightness\"} ")); stream->print(ESPHOME_F("\",channel=\"brightness\"} "));
stream->print(brightness); stream->print(brightness);
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
stream->print(F("esphome_light_color{id=\"")); stream->print(ESPHOME_F("esphome_light_color{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\",channel=\"r\"} ")); stream->print(ESPHOME_F("\",channel=\"r\"} "));
stream->print(r); stream->print(r);
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
stream->print(F("esphome_light_color{id=\"")); stream->print(ESPHOME_F("esphome_light_color{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\",channel=\"g\"} ")); stream->print(ESPHOME_F("\",channel=\"g\"} "));
stream->print(g); stream->print(g);
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
stream->print(F("esphome_light_color{id=\"")); stream->print(ESPHOME_F("esphome_light_color{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\",channel=\"b\"} ")); stream->print(ESPHOME_F("\",channel=\"b\"} "));
stream->print(b); stream->print(b);
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
stream->print(F("esphome_light_color{id=\"")); stream->print(ESPHOME_F("esphome_light_color{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\",channel=\"w\"} ")); stream->print(ESPHOME_F("\",channel=\"w\"} "));
stream->print(w); stream->print(w);
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
// Effect // Effect
std::string effect = obj->get_effect_name(); std::string effect = obj->get_effect_name();
if (effect == "None") { if (effect == "None") {
stream->print(F("esphome_light_effect_active{id=\"")); stream->print(ESPHOME_F("esphome_light_effect_active{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\",effect=\"None\"} 0\n")); stream->print(ESPHOME_F("\",effect=\"None\"} 0\n"));
} else { } else {
stream->print(F("esphome_light_effect_active{id=\"")); stream->print(ESPHOME_F("esphome_light_effect_active{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\",effect=\"")); stream->print(ESPHOME_F("\",effect=\""));
stream->print(effect.c_str()); stream->print(effect.c_str());
stream->print(F("\"} 1\n")); stream->print(ESPHOME_F("\"} 1\n"));
} }
} }
#endif #endif
#ifdef USE_COVER #ifdef USE_COVER
void PrometheusHandler::cover_type_(AsyncResponseStream *stream) { void PrometheusHandler::cover_type_(AsyncResponseStream *stream) {
stream->print(F("#TYPE esphome_cover_value gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_cover_value gauge\n"));
stream->print(F("#TYPE esphome_cover_failed gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_cover_failed gauge\n"));
} }
void PrometheusHandler::cover_row_(AsyncResponseStream *stream, cover::Cover *obj, std::string &area, std::string &node, void PrometheusHandler::cover_row_(AsyncResponseStream *stream, cover::Cover *obj, std::string &area, std::string &node,
std::string &friendly_name) { std::string &friendly_name) {
@@ -393,118 +393,118 @@ void PrometheusHandler::cover_row_(AsyncResponseStream *stream, cover::Cover *ob
return; return;
if (!std::isnan(obj->position)) { if (!std::isnan(obj->position)) {
// We have a valid value, output this value // We have a valid value, output this value
stream->print(F("esphome_cover_failed{id=\"")); stream->print(ESPHOME_F("esphome_cover_failed{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} 0\n")); stream->print(ESPHOME_F("\"} 0\n"));
// Data itself // Data itself
stream->print(F("esphome_cover_value{id=\"")); stream->print(ESPHOME_F("esphome_cover_value{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
stream->print(obj->position); stream->print(obj->position);
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
if (obj->get_traits().get_supports_tilt()) { if (obj->get_traits().get_supports_tilt()) {
stream->print(F("esphome_cover_tilt{id=\"")); stream->print(ESPHOME_F("esphome_cover_tilt{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
stream->print(obj->tilt); stream->print(obj->tilt);
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
} }
} else { } else {
// Invalid state // Invalid state
stream->print(F("esphome_cover_failed{id=\"")); stream->print(ESPHOME_F("esphome_cover_failed{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} 1\n")); stream->print(ESPHOME_F("\"} 1\n"));
} }
} }
#endif #endif
#ifdef USE_SWITCH #ifdef USE_SWITCH
void PrometheusHandler::switch_type_(AsyncResponseStream *stream) { void PrometheusHandler::switch_type_(AsyncResponseStream *stream) {
stream->print(F("#TYPE esphome_switch_value gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_switch_value gauge\n"));
stream->print(F("#TYPE esphome_switch_failed gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_switch_failed gauge\n"));
} }
void PrometheusHandler::switch_row_(AsyncResponseStream *stream, switch_::Switch *obj, std::string &area, void PrometheusHandler::switch_row_(AsyncResponseStream *stream, switch_::Switch *obj, std::string &area,
std::string &node, std::string &friendly_name) { std::string &node, std::string &friendly_name) {
if (obj->is_internal() && !this->include_internal_) if (obj->is_internal() && !this->include_internal_)
return; return;
stream->print(F("esphome_switch_failed{id=\"")); stream->print(ESPHOME_F("esphome_switch_failed{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} 0\n")); stream->print(ESPHOME_F("\"} 0\n"));
// Data itself // Data itself
stream->print(F("esphome_switch_value{id=\"")); stream->print(ESPHOME_F("esphome_switch_value{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
stream->print(obj->state); stream->print(obj->state);
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
} }
#endif #endif
#ifdef USE_LOCK #ifdef USE_LOCK
void PrometheusHandler::lock_type_(AsyncResponseStream *stream) { void PrometheusHandler::lock_type_(AsyncResponseStream *stream) {
stream->print(F("#TYPE esphome_lock_value gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_lock_value gauge\n"));
stream->print(F("#TYPE esphome_lock_failed gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_lock_failed gauge\n"));
} }
void PrometheusHandler::lock_row_(AsyncResponseStream *stream, lock::Lock *obj, std::string &area, std::string &node, void PrometheusHandler::lock_row_(AsyncResponseStream *stream, lock::Lock *obj, std::string &area, std::string &node,
std::string &friendly_name) { std::string &friendly_name) {
if (obj->is_internal() && !this->include_internal_) if (obj->is_internal() && !this->include_internal_)
return; return;
stream->print(F("esphome_lock_failed{id=\"")); stream->print(ESPHOME_F("esphome_lock_failed{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} 0\n")); stream->print(ESPHOME_F("\"} 0\n"));
// Data itself // Data itself
stream->print(F("esphome_lock_value{id=\"")); stream->print(ESPHOME_F("esphome_lock_value{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
stream->print(obj->state); stream->print(obj->state);
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
} }
#endif #endif
// Type-specific implementation // Type-specific implementation
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
void PrometheusHandler::text_sensor_type_(AsyncResponseStream *stream) { void PrometheusHandler::text_sensor_type_(AsyncResponseStream *stream) {
stream->print(F("#TYPE esphome_text_sensor_value gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_text_sensor_value gauge\n"));
stream->print(F("#TYPE esphome_text_sensor_failed gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_text_sensor_failed gauge\n"));
} }
void PrometheusHandler::text_sensor_row_(AsyncResponseStream *stream, text_sensor::TextSensor *obj, std::string &area, void PrometheusHandler::text_sensor_row_(AsyncResponseStream *stream, text_sensor::TextSensor *obj, std::string &area,
std::string &node, std::string &friendly_name) { std::string &node, std::string &friendly_name) {
@@ -512,37 +512,37 @@ void PrometheusHandler::text_sensor_row_(AsyncResponseStream *stream, text_senso
return; return;
if (obj->has_state()) { if (obj->has_state()) {
// We have a valid value, output this value // We have a valid value, output this value
stream->print(F("esphome_text_sensor_failed{id=\"")); stream->print(ESPHOME_F("esphome_text_sensor_failed{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} 0\n")); stream->print(ESPHOME_F("\"} 0\n"));
// Data itself // Data itself
stream->print(F("esphome_text_sensor_value{id=\"")); stream->print(ESPHOME_F("esphome_text_sensor_value{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\",value=\"")); stream->print(ESPHOME_F("\",value=\""));
stream->print(obj->state.c_str()); stream->print(obj->state.c_str());
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
stream->print(F("1.0")); stream->print(ESPHOME_F("1.0"));
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
} else { } else {
// Invalid state // Invalid state
stream->print(F("esphome_text_sensor_failed{id=\"")); stream->print(ESPHOME_F("esphome_text_sensor_failed{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} 1\n")); stream->print(ESPHOME_F("\"} 1\n"));
} }
} }
#endif #endif
@@ -550,8 +550,8 @@ void PrometheusHandler::text_sensor_row_(AsyncResponseStream *stream, text_senso
// Type-specific implementation // Type-specific implementation
#ifdef USE_NUMBER #ifdef USE_NUMBER
void PrometheusHandler::number_type_(AsyncResponseStream *stream) { void PrometheusHandler::number_type_(AsyncResponseStream *stream) {
stream->print(F("#TYPE esphome_number_value gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_number_value gauge\n"));
stream->print(F("#TYPE esphome_number_failed gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_number_failed gauge\n"));
} }
void PrometheusHandler::number_row_(AsyncResponseStream *stream, number::Number *obj, std::string &area, void PrometheusHandler::number_row_(AsyncResponseStream *stream, number::Number *obj, std::string &area,
std::string &node, std::string &friendly_name) { std::string &node, std::string &friendly_name) {
@@ -559,43 +559,43 @@ void PrometheusHandler::number_row_(AsyncResponseStream *stream, number::Number
return; return;
if (!std::isnan(obj->state)) { if (!std::isnan(obj->state)) {
// We have a valid value, output this value // We have a valid value, output this value
stream->print(F("esphome_number_failed{id=\"")); stream->print(ESPHOME_F("esphome_number_failed{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} 0\n")); stream->print(ESPHOME_F("\"} 0\n"));
// Data itself // Data itself
stream->print(F("esphome_number_value{id=\"")); stream->print(ESPHOME_F("esphome_number_value{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
stream->print(obj->state); stream->print(obj->state);
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
} else { } else {
// Invalid state // Invalid state
stream->print(F("esphome_number_failed{id=\"")); stream->print(ESPHOME_F("esphome_number_failed{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} 1\n")); stream->print(ESPHOME_F("\"} 1\n"));
} }
} }
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
void PrometheusHandler::select_type_(AsyncResponseStream *stream) { void PrometheusHandler::select_type_(AsyncResponseStream *stream) {
stream->print(F("#TYPE esphome_select_value gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_select_value gauge\n"));
stream->print(F("#TYPE esphome_select_failed gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_select_failed gauge\n"));
} }
void PrometheusHandler::select_row_(AsyncResponseStream *stream, select::Select *obj, std::string &area, void PrometheusHandler::select_row_(AsyncResponseStream *stream, select::Select *obj, std::string &area,
std::string &node, std::string &friendly_name) { std::string &node, std::string &friendly_name) {
@@ -603,105 +603,105 @@ void PrometheusHandler::select_row_(AsyncResponseStream *stream, select::Select
return; return;
if (obj->has_state()) { if (obj->has_state()) {
// We have a valid value, output this value // We have a valid value, output this value
stream->print(F("esphome_select_failed{id=\"")); stream->print(ESPHOME_F("esphome_select_failed{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} 0\n")); stream->print(ESPHOME_F("\"} 0\n"));
// Data itself // Data itself
stream->print(F("esphome_select_value{id=\"")); stream->print(ESPHOME_F("esphome_select_value{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\",value=\"")); stream->print(ESPHOME_F("\",value=\""));
stream->print(obj->state.c_str()); stream->print(obj->state.c_str());
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
stream->print(F("1.0")); stream->print(ESPHOME_F("1.0"));
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
} else { } else {
// Invalid state // Invalid state
stream->print(F("esphome_select_failed{id=\"")); stream->print(ESPHOME_F("esphome_select_failed{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} 1\n")); stream->print(ESPHOME_F("\"} 1\n"));
} }
} }
#endif #endif
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
void PrometheusHandler::media_player_type_(AsyncResponseStream *stream) { void PrometheusHandler::media_player_type_(AsyncResponseStream *stream) {
stream->print(F("#TYPE esphome_media_player_state_value gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_media_player_state_value gauge\n"));
stream->print(F("#TYPE esphome_media_player_volume gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_media_player_volume gauge\n"));
stream->print(F("#TYPE esphome_media_player_is_muted gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_media_player_is_muted gauge\n"));
stream->print(F("#TYPE esphome_media_player_failed gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_media_player_failed gauge\n"));
} }
void PrometheusHandler::media_player_row_(AsyncResponseStream *stream, media_player::MediaPlayer *obj, void PrometheusHandler::media_player_row_(AsyncResponseStream *stream, media_player::MediaPlayer *obj,
std::string &area, std::string &node, std::string &friendly_name) { std::string &area, std::string &node, std::string &friendly_name) {
if (obj->is_internal() && !this->include_internal_) if (obj->is_internal() && !this->include_internal_)
return; return;
stream->print(F("esphome_media_player_failed{id=\"")); stream->print(ESPHOME_F("esphome_media_player_failed{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} 0\n")); stream->print(ESPHOME_F("\"} 0\n"));
// Data itself // Data itself
stream->print(F("esphome_media_player_state_value{id=\"")); stream->print(ESPHOME_F("esphome_media_player_state_value{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\",value=\"")); stream->print(ESPHOME_F("\",value=\""));
stream->print(media_player::media_player_state_to_string(obj->state)); stream->print(media_player::media_player_state_to_string(obj->state));
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
stream->print(F("1.0")); stream->print(ESPHOME_F("1.0"));
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
stream->print(F("esphome_media_player_volume{id=\"")); stream->print(ESPHOME_F("esphome_media_player_volume{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
stream->print(obj->volume); stream->print(obj->volume);
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
stream->print(F("esphome_media_player_is_muted{id=\"")); stream->print(ESPHOME_F("esphome_media_player_is_muted{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
if (obj->is_muted()) { if (obj->is_muted()) {
stream->print(F("1.0")); stream->print(ESPHOME_F("1.0"));
} else { } else {
stream->print(F("0.0")); stream->print(ESPHOME_F("0.0"));
} }
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
} }
#endif #endif
#ifdef USE_UPDATE #ifdef USE_UPDATE
void PrometheusHandler::update_entity_type_(AsyncResponseStream *stream) { void PrometheusHandler::update_entity_type_(AsyncResponseStream *stream) {
stream->print(F("#TYPE esphome_update_entity_state gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_update_entity_state gauge\n"));
stream->print(F("#TYPE esphome_update_entity_info gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_update_entity_info gauge\n"));
stream->print(F("#TYPE esphome_update_entity_failed gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_update_entity_failed gauge\n"));
} }
void PrometheusHandler::handle_update_state_(AsyncResponseStream *stream, update::UpdateState state) { void PrometheusHandler::handle_update_state_(AsyncResponseStream *stream, update::UpdateState state) {
@@ -730,168 +730,168 @@ void PrometheusHandler::update_entity_row_(AsyncResponseStream *stream, update::
return; return;
if (obj->has_state()) { if (obj->has_state()) {
// We have a valid value, output this value // We have a valid value, output this value
stream->print(F("esphome_update_entity_failed{id=\"")); stream->print(ESPHOME_F("esphome_update_entity_failed{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} 0\n")); stream->print(ESPHOME_F("\"} 0\n"));
// First update state // First update state
stream->print(F("esphome_update_entity_state{id=\"")); stream->print(ESPHOME_F("esphome_update_entity_state{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\",value=\"")); stream->print(ESPHOME_F("\",value=\""));
handle_update_state_(stream, obj->state); handle_update_state_(stream, obj->state);
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
stream->print(F("1.0")); stream->print(ESPHOME_F("1.0"));
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
// Next update info // Next update info
stream->print(F("esphome_update_entity_info{id=\"")); stream->print(ESPHOME_F("esphome_update_entity_info{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\",current_version=\"")); stream->print(ESPHOME_F("\",current_version=\""));
stream->print(obj->update_info.current_version.c_str()); stream->print(obj->update_info.current_version.c_str());
stream->print(F("\",latest_version=\"")); stream->print(ESPHOME_F("\",latest_version=\""));
stream->print(obj->update_info.latest_version.c_str()); stream->print(obj->update_info.latest_version.c_str());
stream->print(F("\",title=\"")); stream->print(ESPHOME_F("\",title=\""));
stream->print(obj->update_info.title.c_str()); stream->print(obj->update_info.title.c_str());
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
stream->print(F("1.0")); stream->print(ESPHOME_F("1.0"));
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
} else { } else {
// Invalid state // Invalid state
stream->print(F("esphome_update_entity_failed{id=\"")); stream->print(ESPHOME_F("esphome_update_entity_failed{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} 1\n")); stream->print(ESPHOME_F("\"} 1\n"));
} }
} }
#endif #endif
#ifdef USE_VALVE #ifdef USE_VALVE
void PrometheusHandler::valve_type_(AsyncResponseStream *stream) { void PrometheusHandler::valve_type_(AsyncResponseStream *stream) {
stream->print(F("#TYPE esphome_valve_operation gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_valve_operation gauge\n"));
stream->print(F("#TYPE esphome_valve_failed gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_valve_failed gauge\n"));
stream->print(F("#TYPE esphome_valve_position gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_valve_position gauge\n"));
} }
void PrometheusHandler::valve_row_(AsyncResponseStream *stream, valve::Valve *obj, std::string &area, std::string &node, void PrometheusHandler::valve_row_(AsyncResponseStream *stream, valve::Valve *obj, std::string &area, std::string &node,
std::string &friendly_name) { std::string &friendly_name) {
if (obj->is_internal() && !this->include_internal_) if (obj->is_internal() && !this->include_internal_)
return; return;
stream->print(F("esphome_valve_failed{id=\"")); stream->print(ESPHOME_F("esphome_valve_failed{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} 0\n")); stream->print(ESPHOME_F("\"} 0\n"));
// Data itself // Data itself
stream->print(F("esphome_valve_operation{id=\"")); stream->print(ESPHOME_F("esphome_valve_operation{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\",operation=\"")); stream->print(ESPHOME_F("\",operation=\""));
stream->print(valve::valve_operation_to_str(obj->current_operation)); stream->print(valve::valve_operation_to_str(obj->current_operation));
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
stream->print(F("1.0")); stream->print(ESPHOME_F("1.0"));
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
// Now see if position is supported // Now see if position is supported
if (obj->get_traits().get_supports_position()) { if (obj->get_traits().get_supports_position()) {
stream->print(F("esphome_valve_position{id=\"")); stream->print(ESPHOME_F("esphome_valve_position{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
stream->print(obj->position); stream->print(obj->position);
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
} }
} }
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
void PrometheusHandler::climate_type_(AsyncResponseStream *stream) { void PrometheusHandler::climate_type_(AsyncResponseStream *stream) {
stream->print(F("#TYPE esphome_climate_setting gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_climate_setting gauge\n"));
stream->print(F("#TYPE esphome_climate_value gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_climate_value gauge\n"));
stream->print(F("#TYPE esphome_climate_failed gauge\n")); stream->print(ESPHOME_F("#TYPE esphome_climate_failed gauge\n"));
} }
void PrometheusHandler::climate_setting_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area, void PrometheusHandler::climate_setting_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area,
std::string &node, std::string &friendly_name, std::string &setting, std::string &node, std::string &friendly_name, std::string &setting,
const LogString *setting_value) { const LogString *setting_value) {
stream->print(F("esphome_climate_setting{id=\"")); stream->print(ESPHOME_F("esphome_climate_setting{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\",category=\"")); stream->print(ESPHOME_F("\",category=\""));
stream->print(setting.c_str()); stream->print(setting.c_str());
stream->print(F("\",setting_value=\"")); stream->print(ESPHOME_F("\",setting_value=\""));
stream->print(LOG_STR_ARG(setting_value)); stream->print(LOG_STR_ARG(setting_value));
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
stream->print(F("1.0")); stream->print(ESPHOME_F("1.0"));
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
} }
void PrometheusHandler::climate_value_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area, void PrometheusHandler::climate_value_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area,
std::string &node, std::string &friendly_name, std::string &category, std::string &node, std::string &friendly_name, std::string &category,
std::string &climate_value) { std::string &climate_value) {
stream->print(F("esphome_climate_value{id=\"")); stream->print(ESPHOME_F("esphome_climate_value{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\",category=\"")); stream->print(ESPHOME_F("\",category=\""));
stream->print(category.c_str()); stream->print(category.c_str());
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
stream->print(climate_value.c_str()); stream->print(climate_value.c_str());
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
} }
void PrometheusHandler::climate_failed_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area, void PrometheusHandler::climate_failed_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area,
std::string &node, std::string &friendly_name, std::string &category, std::string &node, std::string &friendly_name, std::string &category,
bool is_failed_value) { bool is_failed_value) {
stream->print(F("esphome_climate_failed{id=\"")); stream->print(ESPHOME_F("esphome_climate_failed{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
add_area_label_(stream, area); add_area_label_(stream, area);
add_node_label_(stream, node); add_node_label_(stream, node);
add_friendly_name_label_(stream, friendly_name); add_friendly_name_label_(stream, friendly_name);
stream->print(F("\",name=\"")); stream->print(ESPHOME_F("\",name=\""));
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\",category=\"")); stream->print(ESPHOME_F("\",category=\""));
stream->print(category.c_str()); stream->print(category.c_str());
stream->print(F("\"} ")); stream->print(ESPHOME_F("\"} "));
if (is_failed_value) { if (is_failed_value) {
stream->print(F("1.0")); stream->print(ESPHOME_F("1.0"));
} else { } else {
stream->print(F("0.0")); stream->print(ESPHOME_F("0.0"));
} }
stream->print(F("\n")); stream->print(ESPHOME_F("\n"));
} }
void PrometheusHandler::climate_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area, void PrometheusHandler::climate_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area,

View File

@@ -62,6 +62,11 @@ SPIRAM_SPEEDS = {
} }
def supported() -> bool:
variant = get_esp32_variant()
return variant in SPIRAM_MODES
def validate_psram_mode(config): def validate_psram_mode(config):
esp32_config = fv.full_config.get()[PLATFORM_ESP32] esp32_config = fv.full_config.get()[PLATFORM_ESP32]
if config[CONF_SPEED] == "120MHZ": if config[CONF_SPEED] == "120MHZ":
@@ -95,7 +100,7 @@ def get_config_schema(config):
variant = get_esp32_variant() variant = get_esp32_variant()
speeds = [f"{s}MHZ" for s in SPIRAM_SPEEDS.get(variant, [])] speeds = [f"{s}MHZ" for s in SPIRAM_SPEEDS.get(variant, [])]
if not speeds: if not speeds:
return cv.Invalid("PSRAM is not supported on this chip") raise cv.Invalid("PSRAM is not supported on this chip")
modes = SPIRAM_MODES[variant] modes = SPIRAM_MODES[variant]
return cv.Schema( return cv.Schema(
{ {

View File

@@ -8,6 +8,7 @@ namespace esphome {
namespace qmc5883l { namespace qmc5883l {
static const char *const TAG = "qmc5883l"; static const char *const TAG = "qmc5883l";
static const uint8_t QMC5883L_ADDRESS = 0x0D; static const uint8_t QMC5883L_ADDRESS = 0x0D;
static const uint8_t QMC5883L_REGISTER_DATA_X_LSB = 0x00; static const uint8_t QMC5883L_REGISTER_DATA_X_LSB = 0x00;
@@ -32,6 +33,10 @@ void QMC5883LComponent::setup() {
} }
delay(10); delay(10);
if (this->drdy_pin_) {
this->drdy_pin_->setup();
}
uint8_t control_1 = 0; uint8_t control_1 = 0;
control_1 |= 0b01 << 0; // MODE (Mode) -> 0b00=standby, 0b01=continuous control_1 |= 0b01 << 0; // MODE (Mode) -> 0b00=standby, 0b01=continuous
control_1 |= this->datarate_ << 2; control_1 |= this->datarate_ << 2;
@@ -64,6 +69,7 @@ void QMC5883LComponent::setup() {
high_freq_.start(); high_freq_.start();
} }
} }
void QMC5883LComponent::dump_config() { void QMC5883LComponent::dump_config() {
ESP_LOGCONFIG(TAG, "QMC5883L:"); ESP_LOGCONFIG(TAG, "QMC5883L:");
LOG_I2C_DEVICE(this); LOG_I2C_DEVICE(this);
@@ -77,11 +83,20 @@ void QMC5883LComponent::dump_config() {
LOG_SENSOR(" ", "Z Axis", this->z_sensor_); LOG_SENSOR(" ", "Z Axis", this->z_sensor_);
LOG_SENSOR(" ", "Heading", this->heading_sensor_); LOG_SENSOR(" ", "Heading", this->heading_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_PIN(" DRDY Pin: ", this->drdy_pin_);
} }
float QMC5883LComponent::get_setup_priority() const { return setup_priority::DATA; } float QMC5883LComponent::get_setup_priority() const { return setup_priority::DATA; }
void QMC5883LComponent::update() { void QMC5883LComponent::update() {
i2c::ErrorCode err; i2c::ErrorCode err;
uint8_t status = false; uint8_t status = false;
// If DRDY pin is configured and the data is not ready return.
if (this->drdy_pin_ && !this->drdy_pin_->digital_read()) {
return;
}
// Status byte gets cleared when data is read, so we have to read this first. // Status byte gets cleared when data is read, so we have to read this first.
// If status and two axes are desired, it's possible to save one byte of traffic by enabling // If status and two axes are desired, it's possible to save one byte of traffic by enabling
// ROL_PNT in setup and reading 7 bytes starting at the status register. // ROL_PNT in setup and reading 7 bytes starting at the status register.

Some files were not shown because too many files have changed in this diff Show More