mirror of
https://github.com/esphome/esphome.git
synced 2025-10-13 15:23:49 +01:00
Merge branch 'dev' into api_size_limits
This commit is contained in:
@@ -1 +1 @@
|
|||||||
4368db58e8f884aff245996b1e8b644cc0796c0bb2fa706d5740d40b823d3ac9
|
499db61c1aa55b98b6629df603a56a1ba7aff5a9a7c781a5c1552a9dcd186c08
|
||||||
|
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -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
|
||||||
|
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -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}}"
|
||||||
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -102,12 +102,12 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||||
|
|
||||||
- name: Log in to docker hub
|
- name: Log in to docker hub
|
||||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Log in to the GitHub container registry
|
- name: Log in to the GitHub container registry
|
||||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -182,13 +182,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Log in to docker hub
|
- name: Log in to docker hub
|
||||||
if: matrix.registry == 'dockerhub'
|
if: matrix.registry == 'dockerhub'
|
||||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Log in to the GitHub container registry
|
- name: Log in to the GitHub container registry
|
||||||
if: matrix.registry == 'ghcr'
|
if: matrix.registry == 'ghcr'
|
||||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
@@ -1155,7 +1159,9 @@ def parse_args(argv):
|
|||||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||||
)
|
)
|
||||||
|
|
||||||
parser_clean_all = subparsers.add_parser("clean-all", help="Clean all files.")
|
parser_clean_all = subparsers.add_parser(
|
||||||
|
"clean-all", help="Clean all build and platform files."
|
||||||
|
)
|
||||||
parser_clean_all.add_argument(
|
parser_clean_all.add_argument(
|
||||||
"configuration", help="Your YAML configuration directory.", nargs="*"
|
"configuration", help="Your YAML configuration directory.", nargs="*"
|
||||||
)
|
)
|
||||||
|
@@ -12,6 +12,7 @@ from esphome.cpp_generator import ( # noqa: F401
|
|||||||
ArrayInitializer,
|
ArrayInitializer,
|
||||||
Expression,
|
Expression,
|
||||||
LineComment,
|
LineComment,
|
||||||
|
LogStringLiteral,
|
||||||
MockObj,
|
MockObj,
|
||||||
MockObjClass,
|
MockObjClass,
|
||||||
Pvariable,
|
Pvariable,
|
||||||
|
@@ -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_PASSWORD,
|
CONF_PASSWORD,
|
||||||
@@ -59,6 +60,8 @@ CONF_BATCH_DELAY = "batch_delay"
|
|||||||
CONF_CUSTOM_SERVICES = "custom_services"
|
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_MAX_SEND_QUEUE = "max_send_queue"
|
||||||
|
|
||||||
|
|
||||||
def validate_encryption_key(value):
|
def validate_encryption_key(value):
|
||||||
@@ -158,6 +161,42 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_ON_CLIENT_DISCONNECTED): automation.validate_automation(
|
cv.Optional(CONF_ON_CLIENT_DISCONNECTED): automation.validate_automation(
|
||||||
single=True
|
single=True
|
||||||
),
|
),
|
||||||
|
# Connection limits to prevent memory exhaustion on resource-constrained devices
|
||||||
|
# Each connection uses ~500-1000 bytes of RAM plus system resources
|
||||||
|
# Platform defaults based on available RAM and network stack implementation:
|
||||||
|
cv.SplitDefault(
|
||||||
|
CONF_LISTEN_BACKLOG,
|
||||||
|
esp8266=1, # Limited RAM (~40KB free), LWIP raw sockets
|
||||||
|
esp32=4, # More RAM (520KB), BSD sockets
|
||||||
|
rp2040=1, # Limited RAM (264KB), LWIP raw sockets like ESP8266
|
||||||
|
bk72xx=4, # Moderate RAM, BSD-style sockets
|
||||||
|
rtl87xx=4, # Moderate RAM, BSD-style sockets
|
||||||
|
host=4, # Abundant resources
|
||||||
|
ln882x=4, # Moderate RAM
|
||||||
|
): cv.int_range(min=1, max=10),
|
||||||
|
cv.SplitDefault(
|
||||||
|
CONF_MAX_CONNECTIONS,
|
||||||
|
esp8266=4, # ~40KB free RAM, each connection uses ~500-1000 bytes
|
||||||
|
esp32=8, # 520KB RAM available
|
||||||
|
rp2040=4, # 264KB RAM but LWIP constraints
|
||||||
|
bk72xx=8, # Moderate RAM
|
||||||
|
rtl87xx=8, # Moderate RAM
|
||||||
|
host=8, # Abundant resources
|
||||||
|
ln882x=8, # Moderate RAM
|
||||||
|
): 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),
|
||||||
@@ -176,6 +215,11 @@ async def to_code(config):
|
|||||||
cg.add(var.set_password(config[CONF_PASSWORD]))
|
cg.add(var.set_password(config[CONF_PASSWORD]))
|
||||||
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
||||||
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
|
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
|
||||||
|
if CONF_LISTEN_BACKLOG in config:
|
||||||
|
cg.add(var.set_listen_backlog(config[CONF_LISTEN_BACKLOG]))
|
||||||
|
if CONF_MAX_CONNECTIONS in config:
|
||||||
|
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]:
|
||||||
|
@@ -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
|
||||||
@@ -1579,8 +1574,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())
|
||||||
@@ -1599,8 +1593,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
|
||||||
@@ -1609,12 +1602,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();
|
||||||
@@ -1786,8 +1779,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
|
||||||
@@ -1866,12 +1858,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
|
||||||
|
@@ -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
|
||||||
@@ -278,7 +270,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
|
||||||
@@ -739,8 +732,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
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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>
|
||||||
@@ -89,7 +90,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() {
|
||||||
@@ -171,7 +172,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_;
|
||||||
|
|
||||||
@@ -184,7 +185,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_();
|
||||||
|
@@ -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())
|
||||||
|
@@ -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())
|
||||||
|
@@ -87,7 +87,7 @@ void APIServer::setup() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = this->socket_->listen(4);
|
err = this->socket_->listen(this->listen_backlog_);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
|
ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
@@ -140,9 +140,19 @@ void APIServer::loop() {
|
|||||||
while (true) {
|
while (true) {
|
||||||
struct sockaddr_storage source_addr;
|
struct sockaddr_storage source_addr;
|
||||||
socklen_t addr_len = sizeof(source_addr);
|
socklen_t addr_len = sizeof(source_addr);
|
||||||
|
|
||||||
auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
|
auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
|
||||||
if (!sock)
|
if (!sock)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// Check if we're at the connection limit
|
||||||
|
if (this->clients_.size() >= this->max_connections_) {
|
||||||
|
ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, sock->getpeername().c_str());
|
||||||
|
// Immediately close - socket destructor will handle cleanup
|
||||||
|
sock.reset();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str());
|
ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str());
|
||||||
|
|
||||||
auto *conn = new APIConnection(std::move(sock), this);
|
auto *conn = new APIConnection(std::move(sock), this);
|
||||||
@@ -167,7 +177,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
|
||||||
}
|
}
|
||||||
@@ -206,8 +217,10 @@ void APIServer::loop() {
|
|||||||
void APIServer::dump_config() {
|
void APIServer::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
"Server:\n"
|
"Server:\n"
|
||||||
" Address: %s:%u",
|
" Address: %s:%u\n"
|
||||||
network::get_use_address().c_str(), this->port_);
|
" Listen backlog: %u\n"
|
||||||
|
" Max connections: %u",
|
||||||
|
network::get_use_address().c_str(), this->port_, this->listen_backlog_, this->max_connections_);
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
ESP_LOGCONFIG(TAG, " Noise encryption: %s", YESNO(this->noise_ctx_->has_psk()));
|
ESP_LOGCONFIG(TAG, " Noise encryption: %s", YESNO(this->noise_ctx_->has_psk()));
|
||||||
if (!this->noise_ctx_->has_psk()) {
|
if (!this->noise_ctx_->has_psk()) {
|
||||||
|
@@ -44,6 +44,8 @@ class APIServer : public Component, public Controller {
|
|||||||
void set_reboot_timeout(uint32_t reboot_timeout);
|
void set_reboot_timeout(uint32_t reboot_timeout);
|
||||||
void set_batch_delay(uint16_t batch_delay);
|
void set_batch_delay(uint16_t batch_delay);
|
||||||
uint16_t get_batch_delay() const { return batch_delay_; }
|
uint16_t get_batch_delay() const { return batch_delay_; }
|
||||||
|
void set_listen_backlog(uint8_t listen_backlog) { this->listen_backlog_ = listen_backlog; }
|
||||||
|
void set_max_connections(uint8_t max_connections) { this->max_connections_ = max_connections; }
|
||||||
|
|
||||||
// Get reference to shared buffer for API connections
|
// Get reference to shared buffer for API connections
|
||||||
std::vector<uint8_t> &get_shared_buffer_ref() { return shared_write_buffer_; }
|
std::vector<uint8_t> &get_shared_buffer_ref() { return shared_write_buffer_; }
|
||||||
@@ -189,8 +191,12 @@ class APIServer : public Component, public Controller {
|
|||||||
// Group smaller types together
|
// Group smaller types together
|
||||||
uint16_t port_{6053};
|
uint16_t port_{6053};
|
||||||
uint16_t batch_delay_{100};
|
uint16_t batch_delay_{100};
|
||||||
|
// Connection limits - these defaults will be overridden by config values
|
||||||
|
// from cv.SplitDefault in __init__.py which sets platform-specific defaults
|
||||||
|
uint8_t listen_backlog_{4};
|
||||||
|
uint8_t max_connections_{8};
|
||||||
bool shutting_down_ = false;
|
bool shutting_down_ = false;
|
||||||
// 5 bytes used, 3 bytes padding
|
// 7 bytes used, 1 byte padding
|
||||||
|
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>();
|
std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>();
|
||||||
|
@@ -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]))...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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"
|
||||||
|
@@ -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),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@@ -21,8 +21,8 @@ void Canbus::dump_config() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request,
|
canbus::Error Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request,
|
||||||
const std::vector<uint8_t> &data) {
|
const std::vector<uint8_t> &data) {
|
||||||
struct CanFrame can_message;
|
struct CanFrame can_message;
|
||||||
|
|
||||||
uint8_t size = static_cast<uint8_t>(data.size());
|
uint8_t size = static_cast<uint8_t>(data.size());
|
||||||
@@ -45,13 +45,15 @@ void Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transm
|
|||||||
ESP_LOGVV(TAG, " data[%d]=%02x", i, can_message.data[i]);
|
ESP_LOGVV(TAG, " data[%d]=%02x", i, can_message.data[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->send_message(&can_message) != canbus::ERROR_OK) {
|
canbus::Error error = this->send_message(&can_message);
|
||||||
|
if (error != canbus::ERROR_OK) {
|
||||||
if (use_extended_id) {
|
if (use_extended_id) {
|
||||||
ESP_LOGW(TAG, "send to extended id=0x%08" PRIx32 " failed!", can_id);
|
ESP_LOGW(TAG, "send to extended id=0x%08" PRIx32 " failed with error %d!", can_id, error);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "send to standard id=0x%03" PRIx32 " failed!", can_id);
|
ESP_LOGW(TAG, "send to standard id=0x%03" PRIx32 " failed with error %d!", can_id, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Canbus::add_trigger(CanbusTrigger *trigger) {
|
void Canbus::add_trigger(CanbusTrigger *trigger) {
|
||||||
|
@@ -70,11 +70,11 @@ class Canbus : public Component {
|
|||||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|
||||||
void send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request,
|
canbus::Error send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request,
|
||||||
const std::vector<uint8_t> &data);
|
const std::vector<uint8_t> &data);
|
||||||
void send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data) {
|
canbus::Error send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data) {
|
||||||
// for backwards compatibility only
|
// for backwards compatibility only
|
||||||
this->send_data(can_id, use_extended_id, false, data);
|
return this->send_data(can_id, use_extended_id, false, data);
|
||||||
}
|
}
|
||||||
void set_can_id(uint32_t can_id) { this->can_id_ = can_id; }
|
void set_can_id(uint32_t can_id) { this->can_id_ = can_id; }
|
||||||
void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; }
|
void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; }
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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);
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
#include "cover.h"
|
#include "cover.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include <strings.h>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace cover {
|
namespace cover {
|
||||||
|
@@ -197,7 +197,8 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_ESP32_EXT1_WAKEUP): cv.All(
|
cv.Optional(CONF_ESP32_EXT1_WAKEUP): cv.All(
|
||||||
cv.only_on_esp32,
|
cv.only_on_esp32,
|
||||||
esp32.only_on_variant(
|
esp32.only_on_variant(
|
||||||
unsupported=[VARIANT_ESP32C3], msg_prefix="Wakeup from ext1"
|
unsupported=[VARIANT_ESP32C2, VARIANT_ESP32C3],
|
||||||
|
msg_prefix="Wakeup from ext1",
|
||||||
),
|
),
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
@@ -214,7 +215,13 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_TOUCH_WAKEUP): cv.All(
|
cv.Optional(CONF_TOUCH_WAKEUP): cv.All(
|
||||||
cv.only_on_esp32,
|
cv.only_on_esp32,
|
||||||
esp32.only_on_variant(
|
esp32.only_on_variant(
|
||||||
unsupported=[VARIANT_ESP32C3], msg_prefix="Wakeup from touch"
|
unsupported=[
|
||||||
|
VARIANT_ESP32C2,
|
||||||
|
VARIANT_ESP32C3,
|
||||||
|
VARIANT_ESP32C6,
|
||||||
|
VARIANT_ESP32H2,
|
||||||
|
],
|
||||||
|
msg_prefix="Wakeup from touch",
|
||||||
),
|
),
|
||||||
cv.boolean,
|
cv.boolean,
|
||||||
),
|
),
|
||||||
|
@@ -34,7 +34,7 @@ enum WakeupPinMode {
|
|||||||
WAKEUP_PIN_MODE_INVERT_WAKEUP,
|
WAKEUP_PIN_MODE_INVERT_WAKEUP,
|
||||||
};
|
};
|
||||||
|
|
||||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3)
|
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3)
|
||||||
struct Ext1Wakeup {
|
struct Ext1Wakeup {
|
||||||
uint64_t mask;
|
uint64_t mask;
|
||||||
esp_sleep_ext1_wakeup_mode_t wakeup_mode;
|
esp_sleep_ext1_wakeup_mode_t wakeup_mode;
|
||||||
@@ -50,7 +50,7 @@ struct WakeupCauseToRunDuration {
|
|||||||
uint32_t gpio_cause;
|
uint32_t gpio_cause;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif // USE_ESP32
|
||||||
|
|
||||||
template<typename... Ts> class EnterDeepSleepAction;
|
template<typename... Ts> class EnterDeepSleepAction;
|
||||||
|
|
||||||
@@ -73,20 +73,22 @@ class DeepSleepComponent : public Component {
|
|||||||
void set_wakeup_pin(InternalGPIOPin *pin) { this->wakeup_pin_ = pin; }
|
void set_wakeup_pin(InternalGPIOPin *pin) { this->wakeup_pin_ = pin; }
|
||||||
|
|
||||||
void set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode);
|
void set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode);
|
||||||
#endif
|
#endif // USE_ESP32
|
||||||
|
|
||||||
#if defined(USE_ESP32)
|
#if defined(USE_ESP32)
|
||||||
#if !defined(USE_ESP32_VARIANT_ESP32C3)
|
#if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3)
|
||||||
|
|
||||||
void set_ext1_wakeup(Ext1Wakeup ext1_wakeup);
|
void set_ext1_wakeup(Ext1Wakeup ext1_wakeup);
|
||||||
|
|
||||||
void set_touch_wakeup(bool touch_wakeup);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3) && \
|
||||||
|
!defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2)
|
||||||
|
void set_touch_wakeup(bool touch_wakeup);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Set the duration in ms for how long the code should run before entering
|
// Set the duration in ms for how long the code should run before entering
|
||||||
// deep sleep mode, according to the cause the ESP32 has woken.
|
// deep sleep mode, according to the cause the ESP32 has woken.
|
||||||
void set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration);
|
void set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration);
|
||||||
#endif
|
#endif // USE_ESP32
|
||||||
|
|
||||||
/// Set a duration in ms for how long the code should run before entering deep sleep mode.
|
/// Set a duration in ms for how long the code should run before entering deep sleep mode.
|
||||||
void set_run_duration(uint32_t time_ms);
|
void set_run_duration(uint32_t time_ms);
|
||||||
@@ -117,13 +119,13 @@ class DeepSleepComponent : public Component {
|
|||||||
InternalGPIOPin *wakeup_pin_;
|
InternalGPIOPin *wakeup_pin_;
|
||||||
WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE};
|
WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE};
|
||||||
|
|
||||||
#if !defined(USE_ESP32_VARIANT_ESP32C3)
|
#if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3)
|
||||||
optional<Ext1Wakeup> ext1_wakeup_;
|
optional<Ext1Wakeup> ext1_wakeup_;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
optional<bool> touch_wakeup_;
|
optional<bool> touch_wakeup_;
|
||||||
optional<WakeupCauseToRunDuration> wakeup_cause_to_run_duration_;
|
optional<WakeupCauseToRunDuration> wakeup_cause_to_run_duration_;
|
||||||
#endif
|
#endif // USE_ESP32
|
||||||
optional<uint32_t> run_duration_;
|
optional<uint32_t> run_duration_;
|
||||||
bool next_enter_deep_sleep_{false};
|
bool next_enter_deep_sleep_{false};
|
||||||
bool prevent_{false};
|
bool prevent_{false};
|
||||||
|
@@ -7,6 +7,26 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace deep_sleep {
|
namespace deep_sleep {
|
||||||
|
|
||||||
|
// Deep Sleep feature support matrix for ESP32 variants:
|
||||||
|
//
|
||||||
|
// | Variant | ext0 | ext1 | Touch | GPIO wakeup |
|
||||||
|
// |-----------|------|------|-------|-------------|
|
||||||
|
// | ESP32 | ✓ | ✓ | ✓ | |
|
||||||
|
// | ESP32-S2 | ✓ | ✓ | ✓ | |
|
||||||
|
// | ESP32-S3 | ✓ | ✓ | ✓ | |
|
||||||
|
// | ESP32-C2 | | | | ✓ |
|
||||||
|
// | ESP32-C3 | | | | ✓ |
|
||||||
|
// | ESP32-C5 | | (✓) | | (✓) |
|
||||||
|
// | ESP32-C6 | | ✓ | | ✓ |
|
||||||
|
// | ESP32-H2 | | ✓ | | |
|
||||||
|
//
|
||||||
|
// Notes:
|
||||||
|
// - (✓) = Supported by hardware but not yet implemented in ESPHome
|
||||||
|
// - ext0: Single pin wakeup using RTC GPIO (esp_sleep_enable_ext0_wakeup)
|
||||||
|
// - ext1: Multiple pin wakeup (esp_sleep_enable_ext1_wakeup)
|
||||||
|
// - Touch: Touch pad wakeup (esp_sleep_enable_touchpad_wakeup)
|
||||||
|
// - GPIO wakeup: GPIO wakeup for non-RTC pins (esp_deep_sleep_enable_gpio_wakeup)
|
||||||
|
|
||||||
static const char *const TAG = "deep_sleep";
|
static const char *const TAG = "deep_sleep";
|
||||||
|
|
||||||
optional<uint32_t> DeepSleepComponent::get_run_duration_() const {
|
optional<uint32_t> DeepSleepComponent::get_run_duration_() const {
|
||||||
@@ -30,13 +50,13 @@ void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) {
|
|||||||
this->wakeup_pin_mode_ = wakeup_pin_mode;
|
this->wakeup_pin_mode_ = wakeup_pin_mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6)
|
#if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3)
|
||||||
void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wakeup_ = ext1_wakeup; }
|
void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wakeup_ = ext1_wakeup; }
|
||||||
|
|
||||||
#if !defined(USE_ESP32_VARIANT_ESP32H2)
|
|
||||||
void DeepSleepComponent::set_touch_wakeup(bool touch_wakeup) { this->touch_wakeup_ = touch_wakeup; }
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3) && \
|
||||||
|
!defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2)
|
||||||
|
void DeepSleepComponent::set_touch_wakeup(bool touch_wakeup) { this->touch_wakeup_ = touch_wakeup; }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void DeepSleepComponent::set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration) {
|
void DeepSleepComponent::set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration) {
|
||||||
@@ -72,9 +92,13 @@ bool DeepSleepComponent::prepare_to_sleep_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DeepSleepComponent::deep_sleep_() {
|
void DeepSleepComponent::deep_sleep_() {
|
||||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2)
|
// Timer wakeup - all variants support this
|
||||||
if (this->sleep_duration_.has_value())
|
if (this->sleep_duration_.has_value())
|
||||||
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
|
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
|
||||||
|
|
||||||
|
// Single pin wakeup (ext0) - ESP32, S2, S3 only
|
||||||
|
#if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3) && \
|
||||||
|
!defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2)
|
||||||
if (this->wakeup_pin_ != nullptr) {
|
if (this->wakeup_pin_ != nullptr) {
|
||||||
const auto gpio_pin = gpio_num_t(this->wakeup_pin_->get_pin());
|
const auto gpio_pin = gpio_num_t(this->wakeup_pin_->get_pin());
|
||||||
if (this->wakeup_pin_->get_flags() & gpio::FLAG_PULLUP) {
|
if (this->wakeup_pin_->get_flags() & gpio::FLAG_PULLUP) {
|
||||||
@@ -95,32 +119,15 @@ void DeepSleepComponent::deep_sleep_() {
|
|||||||
}
|
}
|
||||||
esp_sleep_enable_ext0_wakeup(gpio_pin, level);
|
esp_sleep_enable_ext0_wakeup(gpio_pin, level);
|
||||||
}
|
}
|
||||||
if (this->ext1_wakeup_.has_value()) {
|
|
||||||
esp_sleep_enable_ext1_wakeup(this->ext1_wakeup_->mask, this->ext1_wakeup_->wakeup_mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->touch_wakeup_.has_value() && *(this->touch_wakeup_)) {
|
|
||||||
esp_sleep_enable_touchpad_wakeup();
|
|
||||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32H2)
|
// GPIO wakeup - C2, C3, C6 only
|
||||||
if (this->sleep_duration_.has_value())
|
#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
||||||
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
|
|
||||||
if (this->ext1_wakeup_.has_value()) {
|
|
||||||
esp_sleep_enable_ext1_wakeup(this->ext1_wakeup_->mask, this->ext1_wakeup_->wakeup_mode);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
|
||||||
if (this->sleep_duration_.has_value())
|
|
||||||
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
|
|
||||||
if (this->wakeup_pin_ != nullptr) {
|
if (this->wakeup_pin_ != nullptr) {
|
||||||
const auto gpio_pin = gpio_num_t(this->wakeup_pin_->get_pin());
|
const auto gpio_pin = gpio_num_t(this->wakeup_pin_->get_pin());
|
||||||
if (this->wakeup_pin_->get_flags() && gpio::FLAG_PULLUP) {
|
if (this->wakeup_pin_->get_flags() & gpio::FLAG_PULLUP) {
|
||||||
gpio_sleep_set_pull_mode(gpio_pin, GPIO_PULLUP_ONLY);
|
gpio_sleep_set_pull_mode(gpio_pin, GPIO_PULLUP_ONLY);
|
||||||
} else if (this->wakeup_pin_->get_flags() && gpio::FLAG_PULLDOWN) {
|
} else if (this->wakeup_pin_->get_flags() & gpio::FLAG_PULLDOWN) {
|
||||||
gpio_sleep_set_pull_mode(gpio_pin, GPIO_PULLDOWN_ONLY);
|
gpio_sleep_set_pull_mode(gpio_pin, GPIO_PULLDOWN_ONLY);
|
||||||
}
|
}
|
||||||
gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT);
|
gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT);
|
||||||
@@ -138,9 +145,26 @@ void DeepSleepComponent::deep_sleep_() {
|
|||||||
static_cast<esp_deepsleep_gpio_wake_up_mode_t>(level));
|
static_cast<esp_deepsleep_gpio_wake_up_mode_t>(level));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Multiple pin wakeup (ext1) - All except C2, C3
|
||||||
|
#if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3)
|
||||||
|
if (this->ext1_wakeup_.has_value()) {
|
||||||
|
esp_sleep_enable_ext1_wakeup(this->ext1_wakeup_->mask, this->ext1_wakeup_->wakeup_mode);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Touch wakeup - ESP32, S2, S3 only
|
||||||
|
#if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3) && \
|
||||||
|
!defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2)
|
||||||
|
if (this->touch_wakeup_.has_value() && *(this->touch_wakeup_)) {
|
||||||
|
esp_sleep_enable_touchpad_wakeup();
|
||||||
|
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
esp_deep_sleep_start();
|
esp_deep_sleep_start();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace deep_sleep
|
} // namespace deep_sleep
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
#endif
|
#endif // USE_ESP32
|
||||||
|
@@ -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
|
||||||
|
@@ -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)
|
||||||
|
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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"
|
||||||
|
@@ -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]);
|
||||||
@@ -208,8 +212,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 +282,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 +295,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);
|
||||||
|
@@ -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,9 @@ 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);
|
||||||
|
|
||||||
|
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 {
|
||||||
|
@@ -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:
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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();
|
||||||
|
@@ -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_{};
|
||||||
|
@@ -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_(
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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")
|
||||||
|
@@ -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 (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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
@@ -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);
|
||||||
|
|
||||||
|
@@ -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 {
|
||||||
|
@@ -1,11 +1,13 @@
|
|||||||
#include "ota_esphome.h"
|
#include "ota_esphome.h"
|
||||||
#ifdef USE_OTA
|
#ifdef USE_OTA
|
||||||
|
#ifdef USE_OTA_PASSWORD
|
||||||
#ifdef USE_OTA_MD5
|
#ifdef USE_OTA_MD5
|
||||||
#include "esphome/components/md5/md5.h"
|
#include "esphome/components/md5/md5.h"
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_OTA_SHA256
|
#ifdef USE_OTA_SHA256
|
||||||
#include "esphome/components/sha256/sha256.h"
|
#include "esphome/components/sha256/sha256.h"
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
#include "esphome/components/network/util.h"
|
#include "esphome/components/network/util.h"
|
||||||
#include "esphome/components/ota/ota_backend.h"
|
#include "esphome/components/ota/ota_backend.h"
|
||||||
#include "esphome/components/ota/ota_backend_arduino_esp32.h"
|
#include "esphome/components/ota/ota_backend_arduino_esp32.h"
|
||||||
@@ -26,9 +28,19 @@ namespace esphome {
|
|||||||
|
|
||||||
static const char *const TAG = "esphome.ota";
|
static const char *const TAG = "esphome.ota";
|
||||||
static constexpr uint16_t OTA_BLOCK_SIZE = 8192;
|
static constexpr uint16_t OTA_BLOCK_SIZE = 8192;
|
||||||
|
static constexpr size_t OTA_BUFFER_SIZE = 1024; // buffer size for OTA data transfer
|
||||||
static constexpr uint32_t OTA_SOCKET_TIMEOUT_HANDSHAKE = 10000; // milliseconds for initial handshake
|
static constexpr uint32_t OTA_SOCKET_TIMEOUT_HANDSHAKE = 10000; // milliseconds for initial handshake
|
||||||
static constexpr uint32_t OTA_SOCKET_TIMEOUT_DATA = 90000; // milliseconds for data transfer
|
static constexpr uint32_t OTA_SOCKET_TIMEOUT_DATA = 90000; // milliseconds for data transfer
|
||||||
|
|
||||||
|
#ifdef USE_OTA_PASSWORD
|
||||||
|
#ifdef USE_OTA_MD5
|
||||||
|
static constexpr size_t MD5_HEX_SIZE = 32; // MD5 hash as hex string (16 bytes * 2)
|
||||||
|
#endif
|
||||||
|
#ifdef USE_OTA_SHA256
|
||||||
|
static constexpr size_t SHA256_HEX_SIZE = 64; // SHA256 hash as hex string (32 bytes * 2)
|
||||||
|
#endif
|
||||||
|
#endif // USE_OTA_PASSWORD
|
||||||
|
|
||||||
void ESPHomeOTAComponent::setup() {
|
void ESPHomeOTAComponent::setup() {
|
||||||
#ifdef USE_OTA_STATE_CALLBACK
|
#ifdef USE_OTA_STATE_CALLBACK
|
||||||
ota::register_ota_platform(this);
|
ota::register_ota_platform(this);
|
||||||
@@ -69,7 +81,7 @@ void ESPHomeOTAComponent::setup() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = this->server_->listen(4);
|
err = this->server_->listen(1); // Only one client at a time
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
this->log_socket_error_(LOG_STR("listen"));
|
this->log_socket_error_(LOG_STR("listen"));
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
@@ -112,11 +124,11 @@ static const uint8_t FEATURE_SUPPORTS_SHA256_AUTH = 0x02;
|
|||||||
#define ALLOW_OTA_DOWNGRADE_MD5
|
#define ALLOW_OTA_DOWNGRADE_MD5
|
||||||
|
|
||||||
void ESPHomeOTAComponent::handle_handshake_() {
|
void ESPHomeOTAComponent::handle_handshake_() {
|
||||||
/// Handle the initial OTA handshake.
|
/// Handle the OTA handshake and authentication.
|
||||||
///
|
///
|
||||||
/// This method is non-blocking and will return immediately if no data is available.
|
/// This method is non-blocking and will return immediately if no data is available.
|
||||||
/// It reads all 5 magic bytes (0x6C, 0x26, 0xF7, 0x5C, 0x45) non-blocking
|
/// It manages the state machine through connection, magic bytes validation, feature
|
||||||
/// before proceeding to handle_data_(). A 10-second timeout is enforced from initial connection.
|
/// negotiation, and authentication before entering the blocking data transfer phase.
|
||||||
|
|
||||||
if (this->client_ == nullptr) {
|
if (this->client_ == nullptr) {
|
||||||
// We already checked server_->ready() in loop(), so we can accept directly
|
// We already checked server_->ready() in loop(), so we can accept directly
|
||||||
@@ -141,7 +153,8 @@ void ESPHomeOTAComponent::handle_handshake_() {
|
|||||||
}
|
}
|
||||||
this->log_start_(LOG_STR("handshake"));
|
this->log_start_(LOG_STR("handshake"));
|
||||||
this->client_connect_time_ = App.get_loop_component_start_time();
|
this->client_connect_time_ = App.get_loop_component_start_time();
|
||||||
this->magic_buf_pos_ = 0; // Reset magic buffer position
|
this->handshake_buf_pos_ = 0; // Reset handshake buffer position
|
||||||
|
this->ota_state_ = OTAState::MAGIC_READ;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for handshake timeout
|
// Check for handshake timeout
|
||||||
@@ -152,46 +165,99 @@ void ESPHomeOTAComponent::handle_handshake_() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to read remaining magic bytes
|
switch (this->ota_state_) {
|
||||||
if (this->magic_buf_pos_ < 5) {
|
case OTAState::MAGIC_READ: {
|
||||||
// Read as many bytes as available
|
// Try to read remaining magic bytes (5 total)
|
||||||
uint8_t bytes_to_read = 5 - this->magic_buf_pos_;
|
if (!this->try_read_(5, LOG_STR("read magic"))) {
|
||||||
ssize_t read = this->client_->read(this->magic_buf_ + this->magic_buf_pos_, bytes_to_read);
|
return;
|
||||||
|
|
||||||
if (read == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
|
|
||||||
return; // No data yet, try again next loop
|
|
||||||
}
|
|
||||||
|
|
||||||
if (read <= 0) {
|
|
||||||
// Error or connection closed
|
|
||||||
if (read == -1) {
|
|
||||||
this->log_socket_error_(LOG_STR("reading magic bytes"));
|
|
||||||
} else {
|
|
||||||
ESP_LOGW(TAG, "Remote closed during handshake");
|
|
||||||
}
|
}
|
||||||
this->cleanup_connection_();
|
|
||||||
return;
|
// Validate magic bytes
|
||||||
|
static const uint8_t MAGIC_BYTES[5] = {0x6C, 0x26, 0xF7, 0x5C, 0x45};
|
||||||
|
if (memcmp(this->handshake_buf_, MAGIC_BYTES, 5) != 0) {
|
||||||
|
ESP_LOGW(TAG, "Magic bytes mismatch! 0x%02X-0x%02X-0x%02X-0x%02X-0x%02X", this->handshake_buf_[0],
|
||||||
|
this->handshake_buf_[1], this->handshake_buf_[2], this->handshake_buf_[3], this->handshake_buf_[4]);
|
||||||
|
this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_MAGIC);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Magic bytes valid, move to next state
|
||||||
|
this->transition_ota_state_(OTAState::MAGIC_ACK);
|
||||||
|
this->handshake_buf_[0] = ota::OTA_RESPONSE_OK;
|
||||||
|
this->handshake_buf_[1] = USE_OTA_VERSION;
|
||||||
|
[[fallthrough]];
|
||||||
}
|
}
|
||||||
|
|
||||||
this->magic_buf_pos_ += read;
|
case OTAState::MAGIC_ACK: {
|
||||||
}
|
// Send OK and version - 2 bytes
|
||||||
|
if (!this->try_write_(2, LOG_STR("ack magic"))) {
|
||||||
// Check if we have all 5 magic bytes
|
return;
|
||||||
if (this->magic_buf_pos_ == 5) {
|
}
|
||||||
// Validate magic bytes
|
// All bytes sent, create backend and move to next state
|
||||||
static const uint8_t MAGIC_BYTES[5] = {0x6C, 0x26, 0xF7, 0x5C, 0x45};
|
this->backend_ = ota::make_ota_backend();
|
||||||
if (memcmp(this->magic_buf_, MAGIC_BYTES, 5) != 0) {
|
this->transition_ota_state_(OTAState::FEATURE_READ);
|
||||||
ESP_LOGW(TAG, "Magic bytes mismatch! 0x%02X-0x%02X-0x%02X-0x%02X-0x%02X", this->magic_buf_[0],
|
[[fallthrough]];
|
||||||
this->magic_buf_[1], this->magic_buf_[2], this->magic_buf_[3], this->magic_buf_[4]);
|
|
||||||
// Send error response (non-blocking, best effort)
|
|
||||||
uint8_t error = static_cast<uint8_t>(ota::OTA_RESPONSE_ERROR_MAGIC);
|
|
||||||
this->client_->write(&error, 1);
|
|
||||||
this->cleanup_connection_();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// All 5 magic bytes are valid, continue with data handling
|
case OTAState::FEATURE_READ: {
|
||||||
this->handle_data_();
|
// Read features - 1 byte
|
||||||
|
if (!this->try_read_(1, LOG_STR("read feature"))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->ota_features_ = this->handshake_buf_[0];
|
||||||
|
ESP_LOGV(TAG, "Features: 0x%02X", this->ota_features_);
|
||||||
|
this->transition_ota_state_(OTAState::FEATURE_ACK);
|
||||||
|
this->handshake_buf_[0] =
|
||||||
|
((this->ota_features_ & FEATURE_SUPPORTS_COMPRESSION) != 0 && this->backend_->supports_compression())
|
||||||
|
? ota::OTA_RESPONSE_SUPPORTS_COMPRESSION
|
||||||
|
: ota::OTA_RESPONSE_HEADER_OK;
|
||||||
|
[[fallthrough]];
|
||||||
|
}
|
||||||
|
|
||||||
|
case OTAState::FEATURE_ACK: {
|
||||||
|
// Acknowledge header - 1 byte
|
||||||
|
if (!this->try_write_(1, LOG_STR("ack feature"))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef USE_OTA_PASSWORD
|
||||||
|
// If password is set, move to auth phase
|
||||||
|
if (!this->password_.empty()) {
|
||||||
|
this->transition_ota_state_(OTAState::AUTH_SEND);
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
// No password, move directly to data phase
|
||||||
|
this->transition_ota_state_(OTAState::DATA);
|
||||||
|
}
|
||||||
|
[[fallthrough]];
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_OTA_PASSWORD
|
||||||
|
case OTAState::AUTH_SEND: {
|
||||||
|
// Non-blocking authentication send
|
||||||
|
if (!this->handle_auth_send_()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->transition_ota_state_(OTAState::AUTH_READ);
|
||||||
|
[[fallthrough]];
|
||||||
|
}
|
||||||
|
|
||||||
|
case OTAState::AUTH_READ: {
|
||||||
|
// Non-blocking authentication read & verify
|
||||||
|
if (!this->handle_auth_read_()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->transition_ota_state_(OTAState::DATA);
|
||||||
|
[[fallthrough]];
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
case OTAState::DATA:
|
||||||
|
this->handle_data_();
|
||||||
|
return;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,114 +265,21 @@ void ESPHomeOTAComponent::handle_data_() {
|
|||||||
/// Handle the OTA data transfer and update process.
|
/// Handle the OTA data transfer and update process.
|
||||||
///
|
///
|
||||||
/// This method is blocking and will not return until the OTA update completes,
|
/// This method is blocking and will not return until the OTA update completes,
|
||||||
/// fails, or times out. It handles authentication, receives the firmware data,
|
/// fails, or times out. It receives the firmware data, writes it to flash,
|
||||||
/// writes it to flash, and reboots on success.
|
/// and reboots on success.
|
||||||
|
///
|
||||||
|
/// Authentication has already been handled in the non-blocking states AUTH_SEND/AUTH_READ.
|
||||||
ota::OTAResponseTypes error_code = ota::OTA_RESPONSE_ERROR_UNKNOWN;
|
ota::OTAResponseTypes error_code = ota::OTA_RESPONSE_ERROR_UNKNOWN;
|
||||||
bool update_started = false;
|
bool update_started = false;
|
||||||
size_t total = 0;
|
size_t total = 0;
|
||||||
uint32_t last_progress = 0;
|
uint32_t last_progress = 0;
|
||||||
uint8_t buf[1024];
|
uint8_t buf[OTA_BUFFER_SIZE];
|
||||||
char *sbuf = reinterpret_cast<char *>(buf);
|
char *sbuf = reinterpret_cast<char *>(buf);
|
||||||
size_t ota_size;
|
size_t ota_size;
|
||||||
uint8_t ota_features;
|
|
||||||
std::unique_ptr<ota::OTABackend> backend;
|
|
||||||
(void) ota_features;
|
|
||||||
#if USE_OTA_VERSION == 2
|
#if USE_OTA_VERSION == 2
|
||||||
size_t size_acknowledged = 0;
|
size_t size_acknowledged = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Send OK and version - 2 bytes
|
|
||||||
buf[0] = ota::OTA_RESPONSE_OK;
|
|
||||||
buf[1] = USE_OTA_VERSION;
|
|
||||||
this->writeall_(buf, 2);
|
|
||||||
|
|
||||||
backend = ota::make_ota_backend();
|
|
||||||
|
|
||||||
// Read features - 1 byte
|
|
||||||
if (!this->readall_(buf, 1)) {
|
|
||||||
this->log_read_error_(LOG_STR("features"));
|
|
||||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
|
||||||
}
|
|
||||||
ota_features = buf[0]; // NOLINT
|
|
||||||
ESP_LOGV(TAG, "Features: 0x%02X", ota_features);
|
|
||||||
|
|
||||||
// Acknowledge header - 1 byte
|
|
||||||
buf[0] = ota::OTA_RESPONSE_HEADER_OK;
|
|
||||||
if ((ota_features & FEATURE_SUPPORTS_COMPRESSION) != 0 && backend->supports_compression()) {
|
|
||||||
buf[0] = ota::OTA_RESPONSE_SUPPORTS_COMPRESSION;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->writeall_(buf, 1);
|
|
||||||
|
|
||||||
#ifdef USE_OTA_PASSWORD
|
|
||||||
if (!this->password_.empty()) {
|
|
||||||
bool auth_success = false;
|
|
||||||
|
|
||||||
#ifdef USE_OTA_SHA256
|
|
||||||
// SECURITY HARDENING: Prefer SHA256 authentication on platforms that support it.
|
|
||||||
//
|
|
||||||
// This is a hardening measure to prevent future downgrade attacks where an attacker
|
|
||||||
// could force the use of MD5 authentication by manipulating the feature flags.
|
|
||||||
//
|
|
||||||
// While MD5 is currently still acceptable for our OTA authentication use case
|
|
||||||
// (where the password is a shared secret and we're only authenticating, not
|
|
||||||
// encrypting), at some point in the future MD5 will likely become so weak that
|
|
||||||
// it could be practically attacked.
|
|
||||||
//
|
|
||||||
// We enforce SHA256 now on capable platforms because:
|
|
||||||
// 1. We can't retroactively update device firmware in the field
|
|
||||||
// 2. Clients (like esphome CLI) can always be updated to support SHA256
|
|
||||||
// 3. This prevents any possibility of downgrade attacks in the future
|
|
||||||
//
|
|
||||||
// Devices that don't support SHA256 (due to platform limitations) will
|
|
||||||
// continue to use MD5 as their only option (see #else branch below).
|
|
||||||
|
|
||||||
bool client_supports_sha256 = (ota_features & FEATURE_SUPPORTS_SHA256_AUTH) != 0;
|
|
||||||
|
|
||||||
#ifdef ALLOW_OTA_DOWNGRADE_MD5
|
|
||||||
// Temporary compatibility mode: Allow MD5 for ~3 versions to enable OTA downgrades
|
|
||||||
// This prevents users from being locked out if they need to downgrade after updating
|
|
||||||
// TODO: Remove this entire ifdef block in 2026.1.0
|
|
||||||
if (client_supports_sha256) {
|
|
||||||
sha256::SHA256 sha_hasher;
|
|
||||||
auth_success = this->perform_hash_auth_(&sha_hasher, this->password_, ota::OTA_RESPONSE_REQUEST_SHA256_AUTH,
|
|
||||||
LOG_STR("SHA256"), sbuf);
|
|
||||||
} else {
|
|
||||||
#ifdef USE_OTA_MD5
|
|
||||||
ESP_LOGW(TAG, "Using MD5 auth for compatibility (deprecated)");
|
|
||||||
md5::MD5Digest md5_hasher;
|
|
||||||
auth_success =
|
|
||||||
this->perform_hash_auth_(&md5_hasher, this->password_, ota::OTA_RESPONSE_REQUEST_AUTH, LOG_STR("MD5"), sbuf);
|
|
||||||
#endif // USE_OTA_MD5
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// Strict mode: SHA256 required on capable platforms (future default)
|
|
||||||
if (!client_supports_sha256) {
|
|
||||||
ESP_LOGW(TAG, "Client requires SHA256");
|
|
||||||
error_code = ota::OTA_RESPONSE_ERROR_AUTH_INVALID;
|
|
||||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
|
||||||
}
|
|
||||||
sha256::SHA256 sha_hasher;
|
|
||||||
auth_success = this->perform_hash_auth_(&sha_hasher, this->password_, ota::OTA_RESPONSE_REQUEST_SHA256_AUTH,
|
|
||||||
LOG_STR("SHA256"), sbuf);
|
|
||||||
#endif // ALLOW_OTA_DOWNGRADE_MD5
|
|
||||||
#else
|
|
||||||
// Platform only supports MD5 - use it as the only available option
|
|
||||||
// This is not a security downgrade as the platform cannot support SHA256
|
|
||||||
#ifdef USE_OTA_MD5
|
|
||||||
md5::MD5Digest md5_hasher;
|
|
||||||
auth_success =
|
|
||||||
this->perform_hash_auth_(&md5_hasher, this->password_, ota::OTA_RESPONSE_REQUEST_AUTH, LOG_STR("MD5"), sbuf);
|
|
||||||
#endif // USE_OTA_MD5
|
|
||||||
#endif // USE_OTA_SHA256
|
|
||||||
|
|
||||||
if (!auth_success) {
|
|
||||||
error_code = ota::OTA_RESPONSE_ERROR_AUTH_INVALID;
|
|
||||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif // USE_OTA_PASSWORD
|
|
||||||
|
|
||||||
// Acknowledge auth OK - 1 byte
|
// Acknowledge auth OK - 1 byte
|
||||||
buf[0] = ota::OTA_RESPONSE_AUTH_OK;
|
buf[0] = ota::OTA_RESPONSE_AUTH_OK;
|
||||||
this->writeall_(buf, 1);
|
this->writeall_(buf, 1);
|
||||||
@@ -334,7 +307,7 @@ void ESPHomeOTAComponent::handle_data_() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// This will block for a few seconds as it locks flash
|
// This will block for a few seconds as it locks flash
|
||||||
error_code = backend->begin(ota_size);
|
error_code = this->backend_->begin(ota_size);
|
||||||
if (error_code != ota::OTA_RESPONSE_OK)
|
if (error_code != ota::OTA_RESPONSE_OK)
|
||||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||||
update_started = true;
|
update_started = true;
|
||||||
@@ -350,7 +323,7 @@ void ESPHomeOTAComponent::handle_data_() {
|
|||||||
}
|
}
|
||||||
sbuf[32] = '\0';
|
sbuf[32] = '\0';
|
||||||
ESP_LOGV(TAG, "Update: Binary MD5 is %s", sbuf);
|
ESP_LOGV(TAG, "Update: Binary MD5 is %s", sbuf);
|
||||||
backend->set_update_md5(sbuf);
|
this->backend_->set_update_md5(sbuf);
|
||||||
|
|
||||||
// Acknowledge MD5 OK - 1 byte
|
// Acknowledge MD5 OK - 1 byte
|
||||||
buf[0] = ota::OTA_RESPONSE_BIN_MD5_OK;
|
buf[0] = ota::OTA_RESPONSE_BIN_MD5_OK;
|
||||||
@@ -358,26 +331,24 @@ void ESPHomeOTAComponent::handle_data_() {
|
|||||||
|
|
||||||
while (total < ota_size) {
|
while (total < ota_size) {
|
||||||
// TODO: timeout check
|
// TODO: timeout check
|
||||||
size_t requested = std::min(sizeof(buf), ota_size - total);
|
size_t remaining = ota_size - total;
|
||||||
|
size_t requested = remaining < OTA_BUFFER_SIZE ? remaining : OTA_BUFFER_SIZE;
|
||||||
ssize_t read = this->client_->read(buf, requested);
|
ssize_t read = this->client_->read(buf, requested);
|
||||||
if (read == -1) {
|
if (read == -1) {
|
||||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
if (this->would_block_(errno)) {
|
||||||
this->yield_and_feed_watchdog_();
|
this->yield_and_feed_watchdog_();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ESP_LOGW(TAG, "Read error, errno %d", errno);
|
ESP_LOGW(TAG, "Read err %d", errno);
|
||||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||||
} else if (read == 0) {
|
} else if (read == 0) {
|
||||||
// $ man recv
|
ESP_LOGW(TAG, "Remote closed");
|
||||||
// "When a stream socket peer has performed an orderly shutdown, the return value will
|
|
||||||
// be 0 (the traditional "end-of-file" return)."
|
|
||||||
ESP_LOGW(TAG, "Remote closed connection");
|
|
||||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||||
}
|
}
|
||||||
|
|
||||||
error_code = backend->write(buf, read);
|
error_code = this->backend_->write(buf, read);
|
||||||
if (error_code != ota::OTA_RESPONSE_OK) {
|
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||||
ESP_LOGW(TAG, "Flash write error, code: %d", error_code);
|
ESP_LOGW(TAG, "Flash write err %d", error_code);
|
||||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||||
}
|
}
|
||||||
total += read;
|
total += read;
|
||||||
@@ -406,9 +377,9 @@ void ESPHomeOTAComponent::handle_data_() {
|
|||||||
buf[0] = ota::OTA_RESPONSE_RECEIVE_OK;
|
buf[0] = ota::OTA_RESPONSE_RECEIVE_OK;
|
||||||
this->writeall_(buf, 1);
|
this->writeall_(buf, 1);
|
||||||
|
|
||||||
error_code = backend->end();
|
error_code = this->backend_->end();
|
||||||
if (error_code != ota::OTA_RESPONSE_OK) {
|
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||||
ESP_LOGW(TAG, "Error ending update! code: %d", error_code);
|
ESP_LOGW(TAG, "End update err %d", error_code);
|
||||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,8 +408,8 @@ error:
|
|||||||
this->writeall_(buf, 1);
|
this->writeall_(buf, 1);
|
||||||
this->cleanup_connection_();
|
this->cleanup_connection_();
|
||||||
|
|
||||||
if (backend != nullptr && update_started) {
|
if (this->backend_ != nullptr && update_started) {
|
||||||
backend->abort();
|
this->backend_->abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
this->status_momentary_error("onerror", 5000);
|
this->status_momentary_error("onerror", 5000);
|
||||||
@@ -459,12 +430,12 @@ bool ESPHomeOTAComponent::readall_(uint8_t *buf, size_t len) {
|
|||||||
|
|
||||||
ssize_t read = this->client_->read(buf + at, len - at);
|
ssize_t read = this->client_->read(buf + at, len - at);
|
||||||
if (read == -1) {
|
if (read == -1) {
|
||||||
if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
if (!this->would_block_(errno)) {
|
||||||
ESP_LOGW(TAG, "Error reading %d bytes, errno %d", len, errno);
|
ESP_LOGW(TAG, "Read err %d bytes, errno %d", len, errno);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (read == 0) {
|
} else if (read == 0) {
|
||||||
ESP_LOGW(TAG, "Remote closed connection");
|
ESP_LOGW(TAG, "Remote closed");
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
at += read;
|
at += read;
|
||||||
@@ -486,8 +457,8 @@ bool ESPHomeOTAComponent::writeall_(const uint8_t *buf, size_t len) {
|
|||||||
|
|
||||||
ssize_t written = this->client_->write(buf + at, len - at);
|
ssize_t written = this->client_->write(buf + at, len - at);
|
||||||
if (written == -1) {
|
if (written == -1) {
|
||||||
if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
if (!this->would_block_(errno)) {
|
||||||
ESP_LOGW(TAG, "Error writing %d bytes, errno %d", len, errno);
|
ESP_LOGW(TAG, "Write err %d bytes, errno %d", len, errno);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -512,11 +483,74 @@ void ESPHomeOTAComponent::log_start_(const LogString *phase) {
|
|||||||
ESP_LOGD(TAG, "Starting %s from %s", LOG_STR_ARG(phase), this->client_->getpeername().c_str());
|
ESP_LOGD(TAG, "Starting %s from %s", LOG_STR_ARG(phase), this->client_->getpeername().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ESPHomeOTAComponent::log_remote_closed_(const LogString *during) {
|
||||||
|
ESP_LOGW(TAG, "Remote closed at %s", LOG_STR_ARG(during));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPHomeOTAComponent::handle_read_error_(ssize_t read, const LogString *desc) {
|
||||||
|
if (read == -1 && this->would_block_(errno)) {
|
||||||
|
return false; // No data yet, try again next loop
|
||||||
|
}
|
||||||
|
|
||||||
|
if (read <= 0) {
|
||||||
|
read == 0 ? this->log_remote_closed_(desc) : this->log_socket_error_(desc);
|
||||||
|
this->cleanup_connection_();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPHomeOTAComponent::handle_write_error_(ssize_t written, const LogString *desc) {
|
||||||
|
if (written == -1) {
|
||||||
|
if (this->would_block_(errno)) {
|
||||||
|
return false; // Try again next loop
|
||||||
|
}
|
||||||
|
this->log_socket_error_(desc);
|
||||||
|
this->cleanup_connection_();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPHomeOTAComponent::try_read_(size_t to_read, const LogString *desc) {
|
||||||
|
// Read bytes into handshake buffer, starting at handshake_buf_pos_
|
||||||
|
size_t bytes_to_read = to_read - this->handshake_buf_pos_;
|
||||||
|
ssize_t read = this->client_->read(this->handshake_buf_ + this->handshake_buf_pos_, bytes_to_read);
|
||||||
|
|
||||||
|
if (!this->handle_read_error_(read, desc)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->handshake_buf_pos_ += read;
|
||||||
|
// Return true only if we have all the requested bytes
|
||||||
|
return this->handshake_buf_pos_ >= to_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPHomeOTAComponent::try_write_(size_t to_write, const LogString *desc) {
|
||||||
|
// Write bytes from handshake buffer, starting at handshake_buf_pos_
|
||||||
|
size_t bytes_to_write = to_write - this->handshake_buf_pos_;
|
||||||
|
ssize_t written = this->client_->write(this->handshake_buf_ + this->handshake_buf_pos_, bytes_to_write);
|
||||||
|
|
||||||
|
if (!this->handle_write_error_(written, desc)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->handshake_buf_pos_ += written;
|
||||||
|
// Return true only if we have written all the requested bytes
|
||||||
|
return this->handshake_buf_pos_ >= to_write;
|
||||||
|
}
|
||||||
|
|
||||||
void ESPHomeOTAComponent::cleanup_connection_() {
|
void ESPHomeOTAComponent::cleanup_connection_() {
|
||||||
this->client_->close();
|
this->client_->close();
|
||||||
this->client_ = nullptr;
|
this->client_ = nullptr;
|
||||||
this->client_connect_time_ = 0;
|
this->client_connect_time_ = 0;
|
||||||
this->magic_buf_pos_ = 0;
|
this->handshake_buf_pos_ = 0;
|
||||||
|
this->ota_state_ = OTAState::IDLE;
|
||||||
|
this->ota_features_ = 0;
|
||||||
|
this->backend_ = nullptr;
|
||||||
|
#ifdef USE_OTA_PASSWORD
|
||||||
|
this->cleanup_auth_();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESPHomeOTAComponent::yield_and_feed_watchdog_() {
|
void ESPHomeOTAComponent::yield_and_feed_watchdog_() {
|
||||||
@@ -525,82 +559,253 @@ void ESPHomeOTAComponent::yield_and_feed_watchdog_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_OTA_PASSWORD
|
#ifdef USE_OTA_PASSWORD
|
||||||
void ESPHomeOTAComponent::log_auth_warning_(const LogString *action, const LogString *hash_name) {
|
void ESPHomeOTAComponent::log_auth_warning_(const LogString *msg) { ESP_LOGW(TAG, "Auth: %s", LOG_STR_ARG(msg)); }
|
||||||
ESP_LOGW(TAG, "Auth: %s %s failed", LOG_STR_ARG(action), LOG_STR_ARG(hash_name));
|
|
||||||
|
bool ESPHomeOTAComponent::select_auth_type_() {
|
||||||
|
#ifdef USE_OTA_SHA256
|
||||||
|
bool client_supports_sha256 = (this->ota_features_ & FEATURE_SUPPORTS_SHA256_AUTH) != 0;
|
||||||
|
|
||||||
|
#ifdef ALLOW_OTA_DOWNGRADE_MD5
|
||||||
|
// Allow fallback to MD5 if client doesn't support SHA256
|
||||||
|
if (client_supports_sha256) {
|
||||||
|
this->auth_type_ = ota::OTA_RESPONSE_REQUEST_SHA256_AUTH;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#ifdef USE_OTA_MD5
|
||||||
|
this->log_auth_warning_(LOG_STR("Using deprecated MD5"));
|
||||||
|
this->auth_type_ = ota::OTA_RESPONSE_REQUEST_AUTH;
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
this->log_auth_warning_(LOG_STR("SHA256 required"));
|
||||||
|
this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_AUTH_INVALID);
|
||||||
|
return false;
|
||||||
|
#endif // USE_OTA_MD5
|
||||||
|
|
||||||
|
#else // !ALLOW_OTA_DOWNGRADE_MD5
|
||||||
|
// Require SHA256
|
||||||
|
if (!client_supports_sha256) {
|
||||||
|
this->log_auth_warning_(LOG_STR("SHA256 required"));
|
||||||
|
this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_AUTH_INVALID);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this->auth_type_ = ota::OTA_RESPONSE_REQUEST_SHA256_AUTH;
|
||||||
|
return true;
|
||||||
|
#endif // ALLOW_OTA_DOWNGRADE_MD5
|
||||||
|
|
||||||
|
#else // !USE_OTA_SHA256
|
||||||
|
#ifdef USE_OTA_MD5
|
||||||
|
// Only MD5 available
|
||||||
|
this->auth_type_ = ota::OTA_RESPONSE_REQUEST_AUTH;
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
// No auth methods available
|
||||||
|
this->log_auth_warning_(LOG_STR("No auth methods available"));
|
||||||
|
this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_AUTH_INVALID);
|
||||||
|
return false;
|
||||||
|
#endif // USE_OTA_MD5
|
||||||
|
#endif // USE_OTA_SHA256
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non-template function definition to reduce binary size
|
bool ESPHomeOTAComponent::handle_auth_send_() {
|
||||||
bool ESPHomeOTAComponent::perform_hash_auth_(HashBase *hasher, const std::string &password, uint8_t auth_request,
|
// Initialize auth buffer if not already done
|
||||||
const LogString *name, char *buf) {
|
if (!this->auth_buf_) {
|
||||||
// Get sizes from the hasher
|
// Select auth type based on client capabilities and configuration
|
||||||
const size_t hex_size = hasher->get_size() * 2; // Hex is twice the byte size
|
if (!this->select_auth_type_()) {
|
||||||
const size_t nonce_len = hasher->get_size() / 4; // Nonce is 1/4 of hash size in bytes
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Use the provided buffer for all hex operations
|
// Generate nonce - hasher must be created and used in same stack frame
|
||||||
|
// 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
|
||||||
|
|
||||||
// Small stack buffer for nonce seed bytes
|
// Declare both hash objects in same stack frame, use pointer to select.
|
||||||
uint8_t nonce_bytes[8]; // Max 8 bytes (2 x uint32_t for SHA256)
|
// 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;
|
||||||
|
|
||||||
// Send auth request type
|
#ifdef USE_OTA_SHA256
|
||||||
this->writeall_(&auth_request, 1);
|
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) {
|
||||||
|
hasher = &sha_hasher;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_OTA_MD5
|
||||||
|
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_AUTH) {
|
||||||
|
hasher = &md5_hasher;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
size_t hex_size = this->get_auth_hex_size_();
|
||||||
|
const size_t to_write = 1 + hex_size;
|
||||||
|
size_t remaining = to_write - this->auth_buf_pos_;
|
||||||
|
|
||||||
|
ssize_t written = this->client_->write(this->auth_buf_.get() + this->auth_buf_pos_, remaining);
|
||||||
|
if (!this->handle_write_error_(written, LOG_STR("ack auth"))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->auth_buf_pos_ += written;
|
||||||
|
|
||||||
|
// Check if we still have more to write
|
||||||
|
if (this->auth_buf_pos_ < to_write) {
|
||||||
|
return false; // More to write, try again next loop
|
||||||
|
}
|
||||||
|
|
||||||
|
// All written, prepare for reading phase
|
||||||
|
this->auth_buf_pos_ = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPHomeOTAComponent::handle_auth_read_() {
|
||||||
|
size_t hex_size = this->get_auth_hex_size_();
|
||||||
|
const size_t to_read = hex_size * 2; // CNonce + Response
|
||||||
|
|
||||||
|
// Try to read remaining bytes (CNonce + Response)
|
||||||
|
// We read cnonce+response starting at offset 1+hex_size (after auth_type and our nonce)
|
||||||
|
size_t cnonce_offset = 1 + hex_size; // Offset where cnonce should be stored in buffer
|
||||||
|
size_t remaining = to_read - this->auth_buf_pos_;
|
||||||
|
ssize_t read = this->client_->read(this->auth_buf_.get() + cnonce_offset + this->auth_buf_pos_, remaining);
|
||||||
|
|
||||||
|
if (!this->handle_read_error_(read, LOG_STR("read auth"))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->auth_buf_pos_ += read;
|
||||||
|
|
||||||
|
// Check if we still need more data
|
||||||
|
if (this->auth_buf_pos_ < to_read) {
|
||||||
|
return false; // More to read, try again next loop
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have all the data, verify it
|
||||||
|
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
|
||||||
|
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) {
|
||||||
|
hasher = &sha_hasher;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_OTA_MD5
|
||||||
|
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_AUTH) {
|
||||||
|
hasher = &md5_hasher;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
hasher->init();
|
hasher->init();
|
||||||
|
hasher->add(this->password_.c_str(), this->password_.length());
|
||||||
// Generate nonce seed bytes using random_bytes
|
hasher->add(nonce, hex_size * 2); // Add both nonce and cnonce (contiguous in buffer)
|
||||||
if (!random_bytes(nonce_bytes, nonce_len)) {
|
|
||||||
this->log_auth_warning_(LOG_STR("Random bytes generation failed"), name);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
hasher->add(nonce_bytes, nonce_len);
|
|
||||||
hasher->calculate();
|
hasher->calculate();
|
||||||
|
|
||||||
// Generate and send nonce
|
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||||
hasher->get_hex(buf);
|
char log_buf[65]; // Fixed size for SHA256 hex (64) + null, works for MD5 (32) too
|
||||||
buf[hex_size] = '\0';
|
// Log CNonce
|
||||||
ESP_LOGV(TAG, "Auth: %s Nonce is %s", LOG_STR_ARG(name), buf);
|
memcpy(log_buf, cnonce, hex_size);
|
||||||
|
log_buf[hex_size] = '\0';
|
||||||
|
ESP_LOGV(TAG, "Auth: CNonce is %s", log_buf);
|
||||||
|
|
||||||
if (!this->writeall_(reinterpret_cast<uint8_t *>(buf), hex_size)) {
|
// Log computed hash
|
||||||
this->log_auth_warning_(LOG_STR("Writing nonce"), name);
|
hasher->get_hex(log_buf);
|
||||||
return false;
|
log_buf[hex_size] = '\0';
|
||||||
}
|
ESP_LOGV(TAG, "Auth: Result is %s", log_buf);
|
||||||
|
|
||||||
// Start challenge: password + nonce
|
// Log received response
|
||||||
hasher->init();
|
memcpy(log_buf, response, hex_size);
|
||||||
hasher->add(password.c_str(), password.length());
|
log_buf[hex_size] = '\0';
|
||||||
hasher->add(buf, hex_size);
|
ESP_LOGV(TAG, "Auth: Response is %s", log_buf);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Read cnonce and add to hash
|
// Compare response
|
||||||
if (!this->readall_(reinterpret_cast<uint8_t *>(buf), hex_size)) {
|
bool matches = hasher->equals_hex(response);
|
||||||
this->log_auth_warning_(LOG_STR("Reading cnonce"), name);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
buf[hex_size] = '\0';
|
|
||||||
ESP_LOGV(TAG, "Auth: %s CNonce is %s", LOG_STR_ARG(name), buf);
|
|
||||||
|
|
||||||
hasher->add(buf, hex_size);
|
|
||||||
hasher->calculate();
|
|
||||||
|
|
||||||
// Log expected result (digest is already in hasher)
|
|
||||||
hasher->get_hex(buf);
|
|
||||||
buf[hex_size] = '\0';
|
|
||||||
ESP_LOGV(TAG, "Auth: %s Result is %s", LOG_STR_ARG(name), buf);
|
|
||||||
|
|
||||||
// Read response into the buffer
|
|
||||||
if (!this->readall_(reinterpret_cast<uint8_t *>(buf), hex_size)) {
|
|
||||||
this->log_auth_warning_(LOG_STR("Reading response"), name);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
buf[hex_size] = '\0';
|
|
||||||
ESP_LOGV(TAG, "Auth: %s Response is %s", LOG_STR_ARG(name), buf);
|
|
||||||
|
|
||||||
// Compare response directly with digest in hasher
|
|
||||||
bool matches = hasher->equals_hex(buf);
|
|
||||||
|
|
||||||
if (!matches) {
|
if (!matches) {
|
||||||
this->log_auth_warning_(LOG_STR("Password mismatch"), name);
|
this->log_auth_warning_(LOG_STR("Password mismatch"));
|
||||||
|
this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_AUTH_INVALID);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return matches;
|
// Authentication successful - clean up auth state
|
||||||
|
this->cleanup_auth_();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ESPHomeOTAComponent::get_auth_hex_size_() const {
|
||||||
|
#ifdef USE_OTA_SHA256
|
||||||
|
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) {
|
||||||
|
return SHA256_HEX_SIZE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_OTA_MD5
|
||||||
|
return MD5_HEX_SIZE;
|
||||||
|
#else
|
||||||
|
#ifndef USE_OTA_SHA256
|
||||||
|
#error "Either USE_OTA_MD5 or USE_OTA_SHA256 must be defined when USE_OTA_PASSWORD is enabled"
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESPHomeOTAComponent::cleanup_auth_() {
|
||||||
|
this->auth_buf_ = nullptr;
|
||||||
|
this->auth_buf_pos_ = 0;
|
||||||
|
this->auth_type_ = 0;
|
||||||
}
|
}
|
||||||
#endif // USE_OTA_PASSWORD
|
#endif // USE_OTA_PASSWORD
|
||||||
|
|
||||||
|
@@ -14,6 +14,18 @@ namespace esphome {
|
|||||||
/// ESPHomeOTAComponent provides a simple way to integrate Over-the-Air updates into your app using ArduinoOTA.
|
/// ESPHomeOTAComponent provides a simple way to integrate Over-the-Air updates into your app using ArduinoOTA.
|
||||||
class ESPHomeOTAComponent : public ota::OTAComponent {
|
class ESPHomeOTAComponent : public ota::OTAComponent {
|
||||||
public:
|
public:
|
||||||
|
enum class OTAState : uint8_t {
|
||||||
|
IDLE,
|
||||||
|
MAGIC_READ, // Reading magic bytes
|
||||||
|
MAGIC_ACK, // Sending OK and version after magic bytes
|
||||||
|
FEATURE_READ, // Reading feature flags from client
|
||||||
|
FEATURE_ACK, // Sending feature acknowledgment
|
||||||
|
#ifdef USE_OTA_PASSWORD
|
||||||
|
AUTH_SEND, // Sending authentication request
|
||||||
|
AUTH_READ, // Reading authentication data
|
||||||
|
#endif // USE_OTA_PASSWORD
|
||||||
|
DATA, // BLOCKING! Processing OTA data (update, etc.)
|
||||||
|
};
|
||||||
#ifdef USE_OTA_PASSWORD
|
#ifdef USE_OTA_PASSWORD
|
||||||
void set_auth_password(const std::string &password) { password_ = password; }
|
void set_auth_password(const std::string &password) { password_ = password; }
|
||||||
#endif // USE_OTA_PASSWORD
|
#endif // USE_OTA_PASSWORD
|
||||||
@@ -32,16 +44,37 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
|
|||||||
void handle_handshake_();
|
void handle_handshake_();
|
||||||
void handle_data_();
|
void handle_data_();
|
||||||
#ifdef USE_OTA_PASSWORD
|
#ifdef USE_OTA_PASSWORD
|
||||||
bool perform_hash_auth_(HashBase *hasher, const std::string &password, uint8_t auth_request, const LogString *name,
|
bool handle_auth_send_();
|
||||||
char *buf);
|
bool handle_auth_read_();
|
||||||
void log_auth_warning_(const LogString *action, const LogString *hash_name);
|
bool select_auth_type_();
|
||||||
|
size_t get_auth_hex_size_() const;
|
||||||
|
void cleanup_auth_();
|
||||||
|
void log_auth_warning_(const LogString *msg);
|
||||||
#endif // USE_OTA_PASSWORD
|
#endif // USE_OTA_PASSWORD
|
||||||
bool readall_(uint8_t *buf, size_t len);
|
bool readall_(uint8_t *buf, size_t len);
|
||||||
bool writeall_(const uint8_t *buf, size_t len);
|
bool writeall_(const uint8_t *buf, size_t len);
|
||||||
|
|
||||||
|
bool try_read_(size_t to_read, const LogString *desc);
|
||||||
|
bool try_write_(size_t to_write, const LogString *desc);
|
||||||
|
|
||||||
|
inline bool would_block_(int error_code) const { return error_code == EAGAIN || error_code == EWOULDBLOCK; }
|
||||||
|
bool handle_read_error_(ssize_t read, const LogString *desc);
|
||||||
|
bool handle_write_error_(ssize_t written, const LogString *desc);
|
||||||
|
inline void transition_ota_state_(OTAState next_state) {
|
||||||
|
this->ota_state_ = next_state;
|
||||||
|
this->handshake_buf_pos_ = 0; // Reset buffer position for next state
|
||||||
|
}
|
||||||
|
|
||||||
void log_socket_error_(const LogString *msg);
|
void log_socket_error_(const LogString *msg);
|
||||||
void log_read_error_(const LogString *what);
|
void log_read_error_(const LogString *what);
|
||||||
void log_start_(const LogString *phase);
|
void log_start_(const LogString *phase);
|
||||||
|
void log_remote_closed_(const LogString *during);
|
||||||
void cleanup_connection_();
|
void cleanup_connection_();
|
||||||
|
inline void send_error_and_cleanup_(ota::OTAResponseTypes error) {
|
||||||
|
uint8_t error_byte = static_cast<uint8_t>(error);
|
||||||
|
this->client_->write(&error_byte, 1); // Best effort, non-blocking
|
||||||
|
this->cleanup_connection_();
|
||||||
|
}
|
||||||
void yield_and_feed_watchdog_();
|
void yield_and_feed_watchdog_();
|
||||||
|
|
||||||
#ifdef USE_OTA_PASSWORD
|
#ifdef USE_OTA_PASSWORD
|
||||||
@@ -50,11 +83,19 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
|
|||||||
|
|
||||||
std::unique_ptr<socket::Socket> server_;
|
std::unique_ptr<socket::Socket> server_;
|
||||||
std::unique_ptr<socket::Socket> client_;
|
std::unique_ptr<socket::Socket> client_;
|
||||||
|
std::unique_ptr<ota::OTABackend> backend_;
|
||||||
|
|
||||||
uint32_t client_connect_time_{0};
|
uint32_t client_connect_time_{0};
|
||||||
uint16_t port_;
|
uint16_t port_;
|
||||||
uint8_t magic_buf_[5];
|
uint8_t handshake_buf_[5];
|
||||||
uint8_t magic_buf_pos_{0};
|
OTAState ota_state_{OTAState::IDLE};
|
||||||
|
uint8_t handshake_buf_pos_{0};
|
||||||
|
uint8_t ota_features_{0};
|
||||||
|
#ifdef USE_OTA_PASSWORD
|
||||||
|
std::unique_ptr<uint8_t[]> auth_buf_;
|
||||||
|
uint8_t auth_buf_pos_{0};
|
||||||
|
uint8_t auth_type_{0}; // Store auth type to know which hasher to use
|
||||||
|
#endif // USE_OTA_PASSWORD
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
@@ -27,6 +27,7 @@ from esphome.const import (
|
|||||||
CONF_GATEWAY,
|
CONF_GATEWAY,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_INTERRUPT_PIN,
|
CONF_INTERRUPT_PIN,
|
||||||
|
CONF_MAC_ADDRESS,
|
||||||
CONF_MANUAL_IP,
|
CONF_MANUAL_IP,
|
||||||
CONF_MISO_PIN,
|
CONF_MISO_PIN,
|
||||||
CONF_MODE,
|
CONF_MODE,
|
||||||
@@ -197,6 +198,7 @@ BASE_SCHEMA = cv.Schema(
|
|||||||
"This option has been removed. Please use the [disabled] option under the "
|
"This option has been removed. Please use the [disabled] option under the "
|
||||||
"new mdns component instead."
|
"new mdns component instead."
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA)
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
@@ -365,6 +367,9 @@ async def to_code(config):
|
|||||||
if phy_define := _PHY_TYPE_TO_DEFINE.get(config[CONF_TYPE]):
|
if phy_define := _PHY_TYPE_TO_DEFINE.get(config[CONF_TYPE]):
|
||||||
cg.add_define(phy_define)
|
cg.add_define(phy_define)
|
||||||
|
|
||||||
|
if mac_address := config.get(CONF_MAC_ADDRESS):
|
||||||
|
cg.add(var.set_fixed_mac(mac_address.parts))
|
||||||
|
|
||||||
cg.add_define("USE_ETHERNET")
|
cg.add_define("USE_ETHERNET")
|
||||||
|
|
||||||
# Disable WiFi when using Ethernet to save memory
|
# Disable WiFi when using Ethernet to save memory
|
||||||
|
@@ -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; \
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +256,11 @@ void EthernetComponent::setup() {
|
|||||||
|
|
||||||
// use ESP internal eth mac
|
// use ESP internal eth mac
|
||||||
uint8_t mac_addr[6];
|
uint8_t mac_addr[6];
|
||||||
esp_read_mac(mac_addr, ESP_MAC_ETH);
|
if (this->fixed_mac_.has_value()) {
|
||||||
|
memcpy(mac_addr, this->fixed_mac_->data(), 6);
|
||||||
|
} else {
|
||||||
|
esp_read_mac(mac_addr, ESP_MAC_ETH);
|
||||||
|
}
|
||||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_S_MAC_ADDR, mac_addr);
|
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_S_MAC_ADDR, mac_addr);
|
||||||
ESPHL_ERROR_CHECK(err, "set mac address error");
|
ESPHL_ERROR_CHECK(err, "set mac address error");
|
||||||
|
|
||||||
|
@@ -84,6 +84,7 @@ class EthernetComponent : public Component {
|
|||||||
#endif
|
#endif
|
||||||
void set_type(EthernetType type);
|
void set_type(EthernetType type);
|
||||||
void set_manual_ip(const ManualIP &manual_ip);
|
void set_manual_ip(const ManualIP &manual_ip);
|
||||||
|
void set_fixed_mac(const std::array<uint8_t, 6> &mac) { this->fixed_mac_ = mac; }
|
||||||
|
|
||||||
network::IPAddresses get_ip_addresses();
|
network::IPAddresses get_ip_addresses();
|
||||||
network::IPAddress get_dns_address(uint8_t num);
|
network::IPAddress get_dns_address(uint8_t num);
|
||||||
@@ -105,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);
|
||||||
@@ -155,12 +157,13 @@ class EthernetComponent : public Component {
|
|||||||
esp_netif_t *eth_netif_{nullptr};
|
esp_netif_t *eth_netif_{nullptr};
|
||||||
esp_eth_handle_t eth_handle_;
|
esp_eth_handle_t eth_handle_;
|
||||||
esp_eth_phy_t *phy_{nullptr};
|
esp_eth_phy_t *phy_{nullptr};
|
||||||
|
optional<std::array<uint8_t, 6>> fixed_mac_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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
|
||||||
|
|
||||||
|
@@ -1,5 +0,0 @@
|
|||||||
CODEOWNERS = ["@Rapsssito"]
|
|
||||||
|
|
||||||
# Allows event_emitter to be configured in yaml, to allow use of the C++ api.
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = {}
|
|
@@ -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
|
|
@@ -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
|
||||||
|
@@ -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};
|
||||||
|
@@ -9,6 +9,7 @@ from esphome.const import (
|
|||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_METHOD,
|
CONF_METHOD,
|
||||||
CONF_ON_ERROR,
|
CONF_ON_ERROR,
|
||||||
|
CONF_ON_RESPONSE,
|
||||||
CONF_TIMEOUT,
|
CONF_TIMEOUT,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_URL,
|
CONF_URL,
|
||||||
@@ -52,7 +53,6 @@ CONF_BUFFER_SIZE_TX = "buffer_size_tx"
|
|||||||
CONF_CA_CERTIFICATE_PATH = "ca_certificate_path"
|
CONF_CA_CERTIFICATE_PATH = "ca_certificate_path"
|
||||||
|
|
||||||
CONF_MAX_RESPONSE_BUFFER_SIZE = "max_response_buffer_size"
|
CONF_MAX_RESPONSE_BUFFER_SIZE = "max_response_buffer_size"
|
||||||
CONF_ON_RESPONSE = "on_response"
|
|
||||||
CONF_HEADERS = "headers"
|
CONF_HEADERS = "headers"
|
||||||
CONF_COLLECT_HEADERS = "collect_headers"
|
CONF_COLLECT_HEADERS = "collect_headers"
|
||||||
CONF_BODY = "body"
|
CONF_BODY = "body"
|
||||||
|
@@ -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
|
||||||
|
@@ -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.
|
||||||
|
@@ -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)))
|
||||||
|
@@ -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) {
|
||||||
|
@@ -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,
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -155,7 +155,7 @@ void MCP2515::prepare_id_(uint8_t *buffer, const bool extended, const uint32_t i
|
|||||||
canid = (uint16_t) (id >> 16);
|
canid = (uint16_t) (id >> 16);
|
||||||
buffer[MCP_SIDL] = (uint8_t) (canid & 0x03);
|
buffer[MCP_SIDL] = (uint8_t) (canid & 0x03);
|
||||||
buffer[MCP_SIDL] += (uint8_t) ((canid & 0x1C) << 3);
|
buffer[MCP_SIDL] += (uint8_t) ((canid & 0x1C) << 3);
|
||||||
buffer[MCP_SIDL] |= TXB_EXIDE_MASK;
|
buffer[MCP_SIDL] |= SIDL_EXIDE_MASK;
|
||||||
buffer[MCP_SIDH] = (uint8_t) (canid >> 5);
|
buffer[MCP_SIDH] = (uint8_t) (canid >> 5);
|
||||||
} else {
|
} else {
|
||||||
buffer[MCP_SIDH] = (uint8_t) (canid >> 3);
|
buffer[MCP_SIDH] = (uint8_t) (canid >> 3);
|
||||||
@@ -258,7 +258,7 @@ canbus::Error MCP2515::send_message(struct canbus::CanFrame *frame) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return canbus::ERROR_FAILTX;
|
return canbus::ERROR_ALLTXBUSY;
|
||||||
}
|
}
|
||||||
|
|
||||||
canbus::Error MCP2515::read_message_(RXBn rxbn, struct canbus::CanFrame *frame) {
|
canbus::Error MCP2515::read_message_(RXBn rxbn, struct canbus::CanFrame *frame) {
|
||||||
@@ -272,7 +272,7 @@ canbus::Error MCP2515::read_message_(RXBn rxbn, struct canbus::CanFrame *frame)
|
|||||||
bool use_extended_id = false;
|
bool use_extended_id = false;
|
||||||
bool remote_transmission_request = false;
|
bool remote_transmission_request = false;
|
||||||
|
|
||||||
if ((tbufdata[MCP_SIDL] & TXB_EXIDE_MASK) == TXB_EXIDE_MASK) {
|
if ((tbufdata[MCP_SIDL] & SIDL_EXIDE_MASK) == SIDL_EXIDE_MASK) {
|
||||||
id = (id << 2) + (tbufdata[MCP_SIDL] & 0x03);
|
id = (id << 2) + (tbufdata[MCP_SIDL] & 0x03);
|
||||||
id = (id << 8) + tbufdata[MCP_EID8];
|
id = (id << 8) + tbufdata[MCP_EID8];
|
||||||
id = (id << 8) + tbufdata[MCP_EID0];
|
id = (id << 8) + tbufdata[MCP_EID0];
|
||||||
@@ -315,6 +315,17 @@ canbus::Error MCP2515::read_message(struct canbus::CanFrame *frame) {
|
|||||||
rc = canbus::ERROR_NOMSG;
|
rc = canbus::ERROR_NOMSG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ESPHOME_LOG_HAS_DEBUG
|
||||||
|
uint8_t err = get_error_flags_();
|
||||||
|
// The receive flowchart in the datasheet says that if rollover is set (BUKT), RX1OVR flag will be set
|
||||||
|
// once both buffers are full. However, the RX0OVR flag is actually set instead.
|
||||||
|
// We can just check for both though because it doesn't break anything.
|
||||||
|
if (err & (EFLG_RX0OVR | EFLG_RX1OVR)) {
|
||||||
|
ESP_LOGD(TAG, "receive buffer overrun");
|
||||||
|
clear_rx_n_ovr_flags_();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -130,7 +130,9 @@ static const uint8_t CANSTAT_ICOD = 0x0E;
|
|||||||
|
|
||||||
static const uint8_t CNF3_SOF = 0x80;
|
static const uint8_t CNF3_SOF = 0x80;
|
||||||
|
|
||||||
static const uint8_t TXB_EXIDE_MASK = 0x08;
|
// applies to RXBn_SIDL, TXBn_SIDL and RXFn_SIDL
|
||||||
|
static const uint8_t SIDL_EXIDE_MASK = 0x08;
|
||||||
|
|
||||||
static const uint8_t DLC_MASK = 0x0F;
|
static const uint8_t DLC_MASK = 0x0F;
|
||||||
static const uint8_t RTR_MASK = 0x40;
|
static const uint8_t RTR_MASK = 0x40;
|
||||||
|
|
||||||
|
@@ -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(
|
||||||
|
@@ -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
|
||||||
|
@@ -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_();
|
||||||
};
|
};
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
|
@@ -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_();
|
||||||
|
@@ -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();
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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_;
|
||||||
|
@@ -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");
|
||||||
|
@@ -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);
|
||||||
};
|
};
|
||||||
|
@@ -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,
|
||||||
|
@@ -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(
|
||||||
{
|
{
|
||||||
|
@@ -40,7 +40,13 @@ void RemoteTransmitterComponent::await_target_time_() {
|
|||||||
if (this->target_time_ == 0) {
|
if (this->target_time_ == 0) {
|
||||||
this->target_time_ = current_time;
|
this->target_time_ = current_time;
|
||||||
} else if ((int32_t) (this->target_time_ - current_time) > 0) {
|
} else if ((int32_t) (this->target_time_ - current_time) > 0) {
|
||||||
|
#if defined(USE_LIBRETINY)
|
||||||
|
// busy loop for libretiny is required (see the comment inside micros() in wiring.c)
|
||||||
|
while ((int32_t) (this->target_time_ - micros()) > 0)
|
||||||
|
;
|
||||||
|
#else
|
||||||
delayMicroseconds(this->target_time_ - current_time);
|
delayMicroseconds(this->target_time_ - current_time);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -374,7 +374,7 @@ void Rtttl::loop() {
|
|||||||
this->last_note_ = millis();
|
this->last_note_ = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
|
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||||
static const LogString *state_to_string(State state) {
|
static const LogString *state_to_string(State state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case STATE_STOPPED:
|
case STATE_STOPPED:
|
||||||
|
@@ -124,7 +124,7 @@ async def to_code(config):
|
|||||||
template, func_args = parameters_to_template(conf[CONF_PARAMETERS])
|
template, func_args = parameters_to_template(conf[CONF_PARAMETERS])
|
||||||
trigger = cg.new_Pvariable(conf[CONF_ID], template)
|
trigger = cg.new_Pvariable(conf[CONF_ID], template)
|
||||||
# Add a human-readable name to the script
|
# Add a human-readable name to the script
|
||||||
cg.add(trigger.set_name(conf[CONF_ID].id))
|
cg.add(trigger.set_name(cg.LogStringLiteral(conf[CONF_ID].id)))
|
||||||
|
|
||||||
if CONF_MAX_RUNS in conf:
|
if CONF_MAX_RUNS in conf:
|
||||||
cg.add(trigger.set_max_runs(conf[CONF_MAX_RUNS]))
|
cg.add(trigger.set_max_runs(conf[CONF_MAX_RUNS]))
|
||||||
|
@@ -48,14 +48,14 @@ template<typename... Ts> class Script : public ScriptLogger, public Trigger<Ts..
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Internal function to give scripts readable names.
|
// Internal function to give scripts readable names.
|
||||||
void set_name(const std::string &name) { name_ = name; }
|
void set_name(const LogString *name) { name_ = name; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
template<int... S> void execute_tuple_(const std::tuple<Ts...> &tuple, seq<S...> /*unused*/) {
|
template<int... S> void execute_tuple_(const std::tuple<Ts...> &tuple, seq<S...> /*unused*/) {
|
||||||
this->execute(std::get<S>(tuple)...);
|
this->execute(std::get<S>(tuple)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string name_;
|
const LogString *name_{nullptr};
|
||||||
};
|
};
|
||||||
|
|
||||||
/** A script type for which only a single instance at a time is allowed.
|
/** A script type for which only a single instance at a time is allowed.
|
||||||
@@ -68,7 +68,7 @@ template<typename... Ts> class SingleScript : public Script<Ts...> {
|
|||||||
void execute(Ts... x) override {
|
void execute(Ts... x) override {
|
||||||
if (this->is_action_running()) {
|
if (this->is_action_running()) {
|
||||||
this->esp_logw_(__LINE__, ESPHOME_LOG_FORMAT("Script '%s' is already running! (mode: single)"),
|
this->esp_logw_(__LINE__, ESPHOME_LOG_FORMAT("Script '%s' is already running! (mode: single)"),
|
||||||
this->name_.c_str());
|
LOG_STR_ARG(this->name_));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ template<typename... Ts> class RestartScript : public Script<Ts...> {
|
|||||||
public:
|
public:
|
||||||
void execute(Ts... x) override {
|
void execute(Ts... x) override {
|
||||||
if (this->is_action_running()) {
|
if (this->is_action_running()) {
|
||||||
this->esp_logd_(__LINE__, ESPHOME_LOG_FORMAT("Script '%s' restarting (mode: restart)"), this->name_.c_str());
|
this->esp_logd_(__LINE__, ESPHOME_LOG_FORMAT("Script '%s' restarting (mode: restart)"), LOG_STR_ARG(this->name_));
|
||||||
this->stop_action();
|
this->stop_action();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,12 +105,12 @@ template<typename... Ts> class QueueingScript : public Script<Ts...>, public Com
|
|||||||
// num_runs_ + 1
|
// num_runs_ + 1
|
||||||
if (this->max_runs_ != 0 && this->num_runs_ + 1 >= this->max_runs_) {
|
if (this->max_runs_ != 0 && this->num_runs_ + 1 >= this->max_runs_) {
|
||||||
this->esp_logw_(__LINE__, ESPHOME_LOG_FORMAT("Script '%s' maximum number of queued runs exceeded!"),
|
this->esp_logw_(__LINE__, ESPHOME_LOG_FORMAT("Script '%s' maximum number of queued runs exceeded!"),
|
||||||
this->name_.c_str());
|
LOG_STR_ARG(this->name_));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->esp_logd_(__LINE__, ESPHOME_LOG_FORMAT("Script '%s' queueing new instance (mode: queued)"),
|
this->esp_logd_(__LINE__, ESPHOME_LOG_FORMAT("Script '%s' queueing new instance (mode: queued)"),
|
||||||
this->name_.c_str());
|
LOG_STR_ARG(this->name_));
|
||||||
this->num_runs_++;
|
this->num_runs_++;
|
||||||
this->var_queue_.push(std::make_tuple(x...));
|
this->var_queue_.push(std::make_tuple(x...));
|
||||||
return;
|
return;
|
||||||
@@ -157,7 +157,7 @@ template<typename... Ts> class ParallelScript : public Script<Ts...> {
|
|||||||
void execute(Ts... x) override {
|
void execute(Ts... x) override {
|
||||||
if (this->max_runs_ != 0 && this->automation_parent_->num_running() >= this->max_runs_) {
|
if (this->max_runs_ != 0 && this->automation_parent_->num_running() >= this->max_runs_) {
|
||||||
this->esp_logw_(__LINE__, ESPHOME_LOG_FORMAT("Script '%s' maximum number of parallel runs exceeded!"),
|
this->esp_logw_(__LINE__, ESPHOME_LOG_FORMAT("Script '%s' maximum number of parallel runs exceeded!"),
|
||||||
this->name_.c_str());
|
LOG_STR_ARG(this->name_));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->trigger(x...);
|
this->trigger(x...);
|
||||||
|
@@ -10,6 +10,39 @@ namespace esphome::sha256 {
|
|||||||
|
|
||||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||||
|
|
||||||
|
// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION REQUIREMENTS:
|
||||||
|
//
|
||||||
|
// The ESP32-S3 uses hardware DMA for SHA acceleration. The mbedtls_sha256_context structure contains
|
||||||
|
// internal state that the DMA engine references. This imposes two critical constraints:
|
||||||
|
//
|
||||||
|
// 1. NO VARIABLE LENGTH ARRAYS (VLAs): VLAs corrupt the stack layout, causing the DMA engine to
|
||||||
|
// write to incorrect memory locations. This results in null pointer dereferences and crashes.
|
||||||
|
// ALWAYS use fixed-size arrays (e.g., char buf[65], not char buf[size+1]).
|
||||||
|
//
|
||||||
|
// 2. SAME STACK FRAME ONLY: The SHA256 object must be created and used entirely within the same
|
||||||
|
// function. NEVER pass the SHA256 object or HashBase pointer to another function. When the stack
|
||||||
|
// frame changes (function call/return), the DMA references become invalid and will produce
|
||||||
|
// truncated hash output (20 bytes instead of 32) or corrupt memory.
|
||||||
|
//
|
||||||
|
// CORRECT USAGE:
|
||||||
|
// void my_function() {
|
||||||
|
// sha256::SHA256 hasher; // Created locally
|
||||||
|
// hasher.init();
|
||||||
|
// hasher.add(data, len); // Any size, no chunking needed
|
||||||
|
// hasher.calculate();
|
||||||
|
// bool ok = hasher.equals_hex(expected);
|
||||||
|
// // hasher destroyed when function returns
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// INCORRECT USAGE (WILL FAIL ON ESP32-S3):
|
||||||
|
// void my_function() {
|
||||||
|
// sha256::SHA256 hasher;
|
||||||
|
// helper(&hasher); // WRONG: Passed to different stack frame
|
||||||
|
// }
|
||||||
|
// void helper(HashBase *h) {
|
||||||
|
// h->init(); // WRONG: Will produce truncated/corrupted output
|
||||||
|
// }
|
||||||
|
|
||||||
SHA256::~SHA256() { mbedtls_sha256_free(&this->ctx_); }
|
SHA256::~SHA256() { mbedtls_sha256_free(&this->ctx_); }
|
||||||
|
|
||||||
void SHA256::init() {
|
void SHA256::init() {
|
||||||
|
@@ -39,6 +39,10 @@ class SHA256 : public esphome::HashBase {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||||
|
// CRITICAL: The mbedtls context MUST be stack-allocated (not a pointer) for ESP32-S3 hardware SHA acceleration.
|
||||||
|
// The ESP32-S3 DMA engine references this structure's memory addresses. If the context is passed to another
|
||||||
|
// function (crossing stack frames) or if VLAs are present, the DMA operations will corrupt memory and produce
|
||||||
|
// truncated/incorrect hash results.
|
||||||
mbedtls_sha256_context ctx_{};
|
mbedtls_sha256_context ctx_{};
|
||||||
#elif defined(USE_ESP8266) || defined(USE_RP2040)
|
#elif defined(USE_ESP8266) || defined(USE_RP2040)
|
||||||
br_sha256_context ctx_{};
|
br_sha256_context ctx_{};
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
#include "lwip/tcp.h"
|
#include "lwip/tcp.h"
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <queue>
|
#include <array>
|
||||||
|
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
@@ -50,12 +50,18 @@ class LWIPRawImpl : public Socket {
|
|||||||
errno = EBADF;
|
errno = EBADF;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
if (accepted_sockets_.empty()) {
|
if (this->accepted_socket_count_ == 0) {
|
||||||
errno = EWOULDBLOCK;
|
errno = EWOULDBLOCK;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
std::unique_ptr<LWIPRawImpl> sock = std::move(accepted_sockets_.front());
|
// Take from front for FIFO ordering
|
||||||
accepted_sockets_.pop();
|
std::unique_ptr<LWIPRawImpl> sock = std::move(this->accepted_sockets_[0]);
|
||||||
|
// Shift remaining sockets forward
|
||||||
|
for (uint8_t i = 1; i < this->accepted_socket_count_; i++) {
|
||||||
|
this->accepted_sockets_[i - 1] = std::move(this->accepted_sockets_[i]);
|
||||||
|
}
|
||||||
|
this->accepted_socket_count_--;
|
||||||
|
LWIP_LOG("Connection accepted by application, queue size: %d", this->accepted_socket_count_);
|
||||||
if (addr != nullptr) {
|
if (addr != nullptr) {
|
||||||
sock->getpeername(addr, addrlen);
|
sock->getpeername(addr, addrlen);
|
||||||
}
|
}
|
||||||
@@ -494,9 +500,18 @@ class LWIPRawImpl : public Socket {
|
|||||||
// nothing to do here, we just don't push it to the queue
|
// nothing to do here, we just don't push it to the queue
|
||||||
return ERR_OK;
|
return ERR_OK;
|
||||||
}
|
}
|
||||||
|
// Check if we've reached the maximum accept queue size
|
||||||
|
if (this->accepted_socket_count_ >= MAX_ACCEPTED_SOCKETS) {
|
||||||
|
LWIP_LOG("Rejecting connection, queue full (%d)", this->accepted_socket_count_);
|
||||||
|
// Abort the connection when queue is full
|
||||||
|
tcp_abort(newpcb);
|
||||||
|
// Must return ERR_ABRT since we called tcp_abort()
|
||||||
|
return ERR_ABRT;
|
||||||
|
}
|
||||||
auto sock = make_unique<LWIPRawImpl>(family_, newpcb);
|
auto sock = make_unique<LWIPRawImpl>(family_, newpcb);
|
||||||
sock->init();
|
sock->init();
|
||||||
accepted_sockets_.push(std::move(sock));
|
this->accepted_sockets_[this->accepted_socket_count_++] = std::move(sock);
|
||||||
|
LWIP_LOG("Accepted connection, queue size: %d", this->accepted_socket_count_);
|
||||||
return ERR_OK;
|
return ERR_OK;
|
||||||
}
|
}
|
||||||
void err_fn(err_t err) {
|
void err_fn(err_t err) {
|
||||||
@@ -587,7 +602,20 @@ class LWIPRawImpl : public Socket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct tcp_pcb *pcb_;
|
struct tcp_pcb *pcb_;
|
||||||
std::queue<std::unique_ptr<LWIPRawImpl>> accepted_sockets_;
|
// Accept queue - holds incoming connections briefly until the event loop calls accept()
|
||||||
|
// This is NOT a connection pool - just a temporary queue between LWIP callbacks and the main loop
|
||||||
|
// 3 slots is plenty since connections are pulled out quickly by the event loop
|
||||||
|
//
|
||||||
|
// Memory analysis: std::array<3> vs original std::queue implementation:
|
||||||
|
// - std::queue uses std::deque internally which on 32-bit systems needs:
|
||||||
|
// 24 bytes (deque object) + 32+ bytes (map array) + heap allocations
|
||||||
|
// Total: ~56+ bytes minimum, plus heap fragmentation
|
||||||
|
// - std::array<3>: 12 bytes fixed (3 pointers × 4 bytes)
|
||||||
|
// Saves ~44+ bytes RAM per listening socket + avoids ALL heap allocations
|
||||||
|
// Used on ESP8266 and RP2040 (platforms using LWIP_TCP implementation)
|
||||||
|
static constexpr size_t MAX_ACCEPTED_SOCKETS = 3;
|
||||||
|
std::array<std::unique_ptr<LWIPRawImpl>, MAX_ACCEPTED_SOCKETS> accepted_sockets_;
|
||||||
|
uint8_t accepted_socket_count_ = 0; // Number of sockets currently in queue
|
||||||
bool rx_closed_ = false;
|
bool rx_closed_ = false;
|
||||||
pbuf *rx_buf_ = nullptr;
|
pbuf *rx_buf_ = nullptr;
|
||||||
size_t rx_buf_offset_ = 0;
|
size_t rx_buf_offset_ = 0;
|
||||||
|
@@ -50,7 +50,7 @@ static const char *const TAG = "sonoff_d1";
|
|||||||
|
|
||||||
uint8_t SonoffD1Output::calc_checksum_(const uint8_t *cmd, const size_t len) {
|
uint8_t SonoffD1Output::calc_checksum_(const uint8_t *cmd, const size_t len) {
|
||||||
uint8_t crc = 0;
|
uint8_t crc = 0;
|
||||||
for (int i = 2; i < len - 1; i++) {
|
for (size_t i = 2; i < len - 1; i++) {
|
||||||
crc += cmd[i];
|
crc += cmd[i];
|
||||||
}
|
}
|
||||||
return crc;
|
return crc;
|
||||||
|
@@ -52,17 +52,19 @@ void SPS30Component::setup() {
|
|||||||
} else {
|
} else {
|
||||||
result = this->write_command(SPS30_CMD_SET_AUTOMATIC_CLEANING_INTERVAL_SECONDS);
|
result = this->write_command(SPS30_CMD_SET_AUTOMATIC_CLEANING_INTERVAL_SECONDS);
|
||||||
}
|
}
|
||||||
if (result) {
|
|
||||||
delay(20);
|
|
||||||
uint16_t secs[2];
|
|
||||||
if (this->read_data(secs, 2)) {
|
|
||||||
this->fan_interval_ = secs[0] << 16 | secs[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this->status_clear_warning();
|
this->set_timeout(20, [this, result]() {
|
||||||
this->skipped_data_read_cycles_ = 0;
|
if (result) {
|
||||||
this->start_continuous_measurement_();
|
uint16_t secs[2];
|
||||||
|
if (this->read_data(secs, 2)) {
|
||||||
|
this->fan_interval_ = secs[0] << 16 | secs[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->status_clear_warning();
|
||||||
|
this->skipped_data_read_cycles_ = 0;
|
||||||
|
this->start_continuous_measurement_();
|
||||||
|
this->setup_complete_ = true;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +113,8 @@ void SPS30Component::dump_config() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SPS30Component::update() {
|
void SPS30Component::update() {
|
||||||
|
if (!this->setup_complete_)
|
||||||
|
return;
|
||||||
/// Check if warning flag active (sensor reconnected?)
|
/// Check if warning flag active (sensor reconnected?)
|
||||||
if (this->status_has_warning()) {
|
if (this->status_has_warning()) {
|
||||||
ESP_LOGD(TAG, "Reconnecting");
|
ESP_LOGD(TAG, "Reconnecting");
|
||||||
|
@@ -30,9 +30,11 @@ class SPS30Component : public PollingComponent, public sensirion_common::Sensiri
|
|||||||
bool start_fan_cleaning();
|
bool start_fan_cleaning();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
bool setup_complete_{false};
|
||||||
uint16_t raw_firmware_version_;
|
uint16_t raw_firmware_version_;
|
||||||
char serial_number_[17] = {0}; /// Terminating NULL character
|
char serial_number_[17] = {0}; /// Terminating NULL character
|
||||||
uint8_t skipped_data_read_cycles_ = 0;
|
uint8_t skipped_data_read_cycles_ = 0;
|
||||||
|
|
||||||
bool start_continuous_measurement_();
|
bool start_continuous_measurement_();
|
||||||
|
|
||||||
enum ErrorCode : uint8_t {
|
enum ErrorCode : uint8_t {
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
|
from ast import literal_eval
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import jinja2 as jinja
|
import jinja2 as jinja
|
||||||
from jinja2.nativetypes import NativeEnvironment
|
from jinja2.sandbox import SandboxedEnvironment
|
||||||
|
|
||||||
TemplateError = jinja.TemplateError
|
TemplateError = jinja.TemplateError
|
||||||
TemplateSyntaxError = jinja.TemplateSyntaxError
|
TemplateSyntaxError = jinja.TemplateSyntaxError
|
||||||
@@ -70,7 +71,7 @@ class Jinja:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, context_vars):
|
def __init__(self, context_vars):
|
||||||
self.env = NativeEnvironment(
|
self.env = SandboxedEnvironment(
|
||||||
trim_blocks=True,
|
trim_blocks=True,
|
||||||
lstrip_blocks=True,
|
lstrip_blocks=True,
|
||||||
block_start_string="<%",
|
block_start_string="<%",
|
||||||
@@ -90,6 +91,15 @@ class Jinja:
|
|||||||
**SAFE_GLOBAL_FUNCTIONS,
|
**SAFE_GLOBAL_FUNCTIONS,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def safe_eval(self, expr):
|
||||||
|
try:
|
||||||
|
result = literal_eval(expr)
|
||||||
|
if not isinstance(result, str):
|
||||||
|
return result
|
||||||
|
except (ValueError, SyntaxError, MemoryError, TypeError):
|
||||||
|
pass
|
||||||
|
return expr
|
||||||
|
|
||||||
def expand(self, content_str):
|
def expand(self, content_str):
|
||||||
"""
|
"""
|
||||||
Renders a string that may contain Jinja expressions or statements
|
Renders a string that may contain Jinja expressions or statements
|
||||||
@@ -106,7 +116,7 @@ class Jinja:
|
|||||||
override_vars = content_str.upvalues
|
override_vars = content_str.upvalues
|
||||||
try:
|
try:
|
||||||
template = self.env.from_string(content_str)
|
template = self.env.from_string(content_str)
|
||||||
result = template.render(override_vars)
|
result = self.safe_eval(template.render(override_vars))
|
||||||
if isinstance(result, Undefined):
|
if isinstance(result, Undefined):
|
||||||
# This happens when the expression is simply an undefined variable. Jinja does not
|
# This happens when the expression is simply an undefined variable. Jinja does not
|
||||||
# raise an exception, instead we get "Undefined".
|
# raise an exception, instead we get "Undefined".
|
||||||
|
@@ -15,6 +15,10 @@ CONF_BANDWIDTH = "bandwidth"
|
|||||||
CONF_BITRATE = "bitrate"
|
CONF_BITRATE = "bitrate"
|
||||||
CONF_CODING_RATE = "coding_rate"
|
CONF_CODING_RATE = "coding_rate"
|
||||||
CONF_CRC_ENABLE = "crc_enable"
|
CONF_CRC_ENABLE = "crc_enable"
|
||||||
|
CONF_CRC_INVERTED = "crc_inverted"
|
||||||
|
CONF_CRC_SIZE = "crc_size"
|
||||||
|
CONF_CRC_POLYNOMIAL = "crc_polynomial"
|
||||||
|
CONF_CRC_INITIAL = "crc_initial"
|
||||||
CONF_DEVIATION = "deviation"
|
CONF_DEVIATION = "deviation"
|
||||||
CONF_DIO1_PIN = "dio1_pin"
|
CONF_DIO1_PIN = "dio1_pin"
|
||||||
CONF_HW_VERSION = "hw_version"
|
CONF_HW_VERSION = "hw_version"
|
||||||
@@ -188,6 +192,14 @@ CONFIG_SCHEMA = (
|
|||||||
cv.Required(CONF_BUSY_PIN): pins.internal_gpio_input_pin_schema,
|
cv.Required(CONF_BUSY_PIN): pins.internal_gpio_input_pin_schema,
|
||||||
cv.Optional(CONF_CODING_RATE, default="CR_4_5"): cv.enum(CODING_RATE),
|
cv.Optional(CONF_CODING_RATE, default="CR_4_5"): cv.enum(CODING_RATE),
|
||||||
cv.Optional(CONF_CRC_ENABLE, default=False): cv.boolean,
|
cv.Optional(CONF_CRC_ENABLE, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_CRC_INVERTED, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_CRC_SIZE, default=2): cv.int_range(min=1, max=2),
|
||||||
|
cv.Optional(CONF_CRC_POLYNOMIAL, default=0x1021): cv.All(
|
||||||
|
cv.hex_int, cv.Range(min=0, max=0xFFFF)
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_CRC_INITIAL, default=0x1D0F): cv.All(
|
||||||
|
cv.hex_int, cv.Range(min=0, max=0xFFFF)
|
||||||
|
),
|
||||||
cv.Optional(CONF_DEVIATION, default=5000): cv.int_range(min=0, max=100000),
|
cv.Optional(CONF_DEVIATION, default=5000): cv.int_range(min=0, max=100000),
|
||||||
cv.Required(CONF_DIO1_PIN): pins.internal_gpio_input_pin_schema,
|
cv.Required(CONF_DIO1_PIN): pins.internal_gpio_input_pin_schema,
|
||||||
cv.Required(CONF_FREQUENCY): cv.int_range(min=137000000, max=1020000000),
|
cv.Required(CONF_FREQUENCY): cv.int_range(min=137000000, max=1020000000),
|
||||||
@@ -251,6 +263,10 @@ async def to_code(config):
|
|||||||
cg.add(var.set_shaping(config[CONF_SHAPING]))
|
cg.add(var.set_shaping(config[CONF_SHAPING]))
|
||||||
cg.add(var.set_bitrate(config[CONF_BITRATE]))
|
cg.add(var.set_bitrate(config[CONF_BITRATE]))
|
||||||
cg.add(var.set_crc_enable(config[CONF_CRC_ENABLE]))
|
cg.add(var.set_crc_enable(config[CONF_CRC_ENABLE]))
|
||||||
|
cg.add(var.set_crc_inverted(config[CONF_CRC_INVERTED]))
|
||||||
|
cg.add(var.set_crc_size(config[CONF_CRC_SIZE]))
|
||||||
|
cg.add(var.set_crc_polynomial(config[CONF_CRC_POLYNOMIAL]))
|
||||||
|
cg.add(var.set_crc_initial(config[CONF_CRC_INITIAL]))
|
||||||
cg.add(var.set_payload_length(config[CONF_PAYLOAD_LENGTH]))
|
cg.add(var.set_payload_length(config[CONF_PAYLOAD_LENGTH]))
|
||||||
cg.add(var.set_preamble_size(config[CONF_PREAMBLE_SIZE]))
|
cg.add(var.set_preamble_size(config[CONF_PREAMBLE_SIZE]))
|
||||||
cg.add(var.set_preamble_detect(config[CONF_PREAMBLE_DETECT]))
|
cg.add(var.set_preamble_detect(config[CONF_PREAMBLE_DETECT]))
|
||||||
|
@@ -235,6 +235,16 @@ void SX126x::configure() {
|
|||||||
buf[7] = (fdev >> 0) & 0xFF;
|
buf[7] = (fdev >> 0) & 0xFF;
|
||||||
this->write_opcode_(RADIO_SET_MODULATIONPARAMS, buf, 8);
|
this->write_opcode_(RADIO_SET_MODULATIONPARAMS, buf, 8);
|
||||||
|
|
||||||
|
// set crc params
|
||||||
|
if (this->crc_enable_) {
|
||||||
|
buf[0] = this->crc_initial_ >> 8;
|
||||||
|
buf[1] = this->crc_initial_ & 0xFF;
|
||||||
|
this->write_register_(REG_CRC_INITIAL, buf, 2);
|
||||||
|
buf[0] = this->crc_polynomial_ >> 8;
|
||||||
|
buf[1] = this->crc_polynomial_ & 0xFF;
|
||||||
|
this->write_register_(REG_CRC_POLYNOMIAL, buf, 2);
|
||||||
|
}
|
||||||
|
|
||||||
// set packet params and sync word
|
// set packet params and sync word
|
||||||
this->set_packet_params_(this->get_max_packet_size());
|
this->set_packet_params_(this->get_max_packet_size());
|
||||||
if (!this->sync_value_.empty()) {
|
if (!this->sync_value_.empty()) {
|
||||||
@@ -276,7 +286,11 @@ void SX126x::set_packet_params_(uint8_t payload_length) {
|
|||||||
buf[4] = 0x00;
|
buf[4] = 0x00;
|
||||||
buf[5] = (this->payload_length_ > 0) ? 0x00 : 0x01;
|
buf[5] = (this->payload_length_ > 0) ? 0x00 : 0x01;
|
||||||
buf[6] = payload_length;
|
buf[6] = payload_length;
|
||||||
buf[7] = this->crc_enable_ ? 0x06 : 0x01;
|
if (this->crc_enable_) {
|
||||||
|
buf[7] = (this->crc_inverted_ ? 0x04 : 0x00) + (this->crc_size_ & 0x02);
|
||||||
|
} else {
|
||||||
|
buf[7] = 0x01;
|
||||||
|
}
|
||||||
buf[8] = 0x00;
|
buf[8] = 0x00;
|
||||||
this->write_opcode_(RADIO_SET_PACKETPARAMS, buf, 9);
|
this->write_opcode_(RADIO_SET_PACKETPARAMS, buf, 9);
|
||||||
}
|
}
|
||||||
|
@@ -67,6 +67,10 @@ class SX126x : public Component,
|
|||||||
void set_busy_pin(InternalGPIOPin *busy_pin) { this->busy_pin_ = busy_pin; }
|
void set_busy_pin(InternalGPIOPin *busy_pin) { this->busy_pin_ = busy_pin; }
|
||||||
void set_coding_rate(uint8_t coding_rate) { this->coding_rate_ = coding_rate; }
|
void set_coding_rate(uint8_t coding_rate) { this->coding_rate_ = coding_rate; }
|
||||||
void set_crc_enable(bool crc_enable) { this->crc_enable_ = crc_enable; }
|
void set_crc_enable(bool crc_enable) { this->crc_enable_ = crc_enable; }
|
||||||
|
void set_crc_inverted(bool crc_inverted) { this->crc_inverted_ = crc_inverted; }
|
||||||
|
void set_crc_size(uint8_t crc_size) { this->crc_size_ = crc_size; }
|
||||||
|
void set_crc_polynomial(uint16_t crc_polynomial) { this->crc_polynomial_ = crc_polynomial; }
|
||||||
|
void set_crc_initial(uint16_t crc_initial) { this->crc_initial_ = crc_initial; }
|
||||||
void set_deviation(uint32_t deviation) { this->deviation_ = deviation; }
|
void set_deviation(uint32_t deviation) { this->deviation_ = deviation; }
|
||||||
void set_dio1_pin(InternalGPIOPin *dio1_pin) { this->dio1_pin_ = dio1_pin; }
|
void set_dio1_pin(InternalGPIOPin *dio1_pin) { this->dio1_pin_ = dio1_pin; }
|
||||||
void set_frequency(uint32_t frequency) { this->frequency_ = frequency; }
|
void set_frequency(uint32_t frequency) { this->frequency_ = frequency; }
|
||||||
@@ -118,6 +122,11 @@ class SX126x : public Component,
|
|||||||
char version_[16];
|
char version_[16];
|
||||||
SX126xBw bandwidth_{SX126X_BW_125000};
|
SX126xBw bandwidth_{SX126X_BW_125000};
|
||||||
uint32_t bitrate_{0};
|
uint32_t bitrate_{0};
|
||||||
|
bool crc_enable_{false};
|
||||||
|
bool crc_inverted_{false};
|
||||||
|
uint8_t crc_size_{0};
|
||||||
|
uint16_t crc_polynomial_{0};
|
||||||
|
uint16_t crc_initial_{0};
|
||||||
uint32_t deviation_{0};
|
uint32_t deviation_{0};
|
||||||
uint32_t frequency_{0};
|
uint32_t frequency_{0};
|
||||||
uint32_t payload_length_{0};
|
uint32_t payload_length_{0};
|
||||||
@@ -131,7 +140,6 @@ class SX126x : public Component,
|
|||||||
uint8_t shaping_{0};
|
uint8_t shaping_{0};
|
||||||
uint8_t spreading_factor_{0};
|
uint8_t spreading_factor_{0};
|
||||||
int8_t pa_power_{0};
|
int8_t pa_power_{0};
|
||||||
bool crc_enable_{false};
|
|
||||||
bool rx_start_{false};
|
bool rx_start_{false};
|
||||||
bool rf_switch_{false};
|
bool rf_switch_{false};
|
||||||
};
|
};
|
||||||
|
@@ -53,6 +53,8 @@ enum SX126xOpCode : uint8_t {
|
|||||||
|
|
||||||
enum SX126xRegister : uint16_t {
|
enum SX126xRegister : uint16_t {
|
||||||
REG_VERSION_STRING = 0x0320,
|
REG_VERSION_STRING = 0x0320,
|
||||||
|
REG_CRC_INITIAL = 0x06BC,
|
||||||
|
REG_CRC_POLYNOMIAL = 0x06BE,
|
||||||
REG_GFSK_SYNCWORD = 0x06C0,
|
REG_GFSK_SYNCWORD = 0x06C0,
|
||||||
REG_LORA_SYNCWORD = 0x0740,
|
REG_LORA_SYNCWORD = 0x0740,
|
||||||
REG_OCP = 0x08E7,
|
REG_OCP = 0x08E7,
|
||||||
|
@@ -50,7 +50,7 @@ void TuyaSelect::dump_config() {
|
|||||||
" Options are:",
|
" Options are:",
|
||||||
this->select_id_, this->is_int_ ? "int" : "enum");
|
this->select_id_, this->is_int_ ? "int" : "enum");
|
||||||
auto options = this->traits.get_options();
|
auto options = this->traits.get_options();
|
||||||
for (auto i = 0; i < this->mappings_.size(); i++) {
|
for (size_t i = 0; i < this->mappings_.size(); i++) {
|
||||||
ESP_LOGCONFIG(TAG, " %i: %s", this->mappings_.at(i), options.at(i).c_str());
|
ESP_LOGCONFIG(TAG, " %i: %s", this->mappings_.at(i), options.at(i).c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
#include "valve.h"
|
#include "valve.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include <strings.h>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace valve {
|
namespace valve {
|
||||||
|
@@ -242,7 +242,6 @@ void VoiceAssistant::loop() {
|
|||||||
msg.flags = flags;
|
msg.flags = flags;
|
||||||
msg.audio_settings = audio_settings;
|
msg.audio_settings = audio_settings;
|
||||||
msg.set_wake_word_phrase(StringRef(this->wake_word_));
|
msg.set_wake_word_phrase(StringRef(this->wake_word_));
|
||||||
this->wake_word_ = "";
|
|
||||||
|
|
||||||
// Reset media player state tracking
|
// Reset media player state tracking
|
||||||
#ifdef USE_MEDIA_PLAYER
|
#ifdef USE_MEDIA_PLAYER
|
||||||
@@ -430,8 +429,9 @@ void VoiceAssistant::client_subscription(api::APIConnection *client, bool subscr
|
|||||||
|
|
||||||
if (this->api_client_ != nullptr) {
|
if (this->api_client_ != nullptr) {
|
||||||
ESP_LOGE(TAG, "Multiple API Clients attempting to connect to Voice Assistant");
|
ESP_LOGE(TAG, "Multiple API Clients attempting to connect to Voice Assistant");
|
||||||
ESP_LOGE(TAG, "Current client: %s", this->api_client_->get_client_combined_info().c_str());
|
ESP_LOGE(TAG, "Current client: %s (%s)", this->api_client_->get_name().c_str(),
|
||||||
ESP_LOGE(TAG, "New client: %s", client->get_client_combined_info().c_str());
|
this->api_client_->get_peername().c_str());
|
||||||
|
ESP_LOGE(TAG, "New client: %s (%s)", client->get_name().c_str(), client->get_peername().c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,13 +9,12 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace web_server {
|
namespace web_server {
|
||||||
|
|
||||||
#ifdef USE_ARDUINO
|
#ifdef USE_ESP32
|
||||||
|
ListEntitiesIterator::ListEntitiesIterator(const WebServer *ws, AsyncEventSource *es) : web_server_(ws), events_(es) {}
|
||||||
|
#elif USE_ARDUINO
|
||||||
ListEntitiesIterator::ListEntitiesIterator(const WebServer *ws, DeferredUpdateEventSource *es)
|
ListEntitiesIterator::ListEntitiesIterator(const WebServer *ws, DeferredUpdateEventSource *es)
|
||||||
: web_server_(ws), events_(es) {}
|
: web_server_(ws), events_(es) {}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_ESP_IDF
|
|
||||||
ListEntitiesIterator::ListEntitiesIterator(const WebServer *ws, AsyncEventSource *es) : web_server_(ws), events_(es) {}
|
|
||||||
#endif
|
|
||||||
ListEntitiesIterator::~ListEntitiesIterator() {}
|
ListEntitiesIterator::~ListEntitiesIterator() {}
|
||||||
|
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
|
@@ -5,25 +5,24 @@
|
|||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/component_iterator.h"
|
#include "esphome/core/component_iterator.h"
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
#ifdef USE_ESP_IDF
|
#ifdef USE_ESP32
|
||||||
namespace web_server_idf {
|
namespace web_server_idf {
|
||||||
class AsyncEventSource;
|
class AsyncEventSource;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
namespace web_server {
|
namespace web_server {
|
||||||
|
|
||||||
#ifdef USE_ARDUINO
|
#if !defined(USE_ESP32) && defined(USE_ARDUINO)
|
||||||
class DeferredUpdateEventSource;
|
class DeferredUpdateEventSource;
|
||||||
#endif
|
#endif
|
||||||
class WebServer;
|
class WebServer;
|
||||||
|
|
||||||
class ListEntitiesIterator : public ComponentIterator {
|
class ListEntitiesIterator : public ComponentIterator {
|
||||||
public:
|
public:
|
||||||
#ifdef USE_ARDUINO
|
#ifdef USE_ESP32
|
||||||
ListEntitiesIterator(const WebServer *ws, DeferredUpdateEventSource *es);
|
|
||||||
#endif
|
|
||||||
#ifdef USE_ESP_IDF
|
|
||||||
ListEntitiesIterator(const WebServer *ws, esphome::web_server_idf::AsyncEventSource *es);
|
ListEntitiesIterator(const WebServer *ws, esphome::web_server_idf::AsyncEventSource *es);
|
||||||
|
#elif defined(USE_ARDUINO)
|
||||||
|
ListEntitiesIterator(const WebServer *ws, DeferredUpdateEventSource *es);
|
||||||
#endif
|
#endif
|
||||||
virtual ~ListEntitiesIterator();
|
virtual ~ListEntitiesIterator();
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
@@ -90,11 +89,10 @@ class ListEntitiesIterator : public ComponentIterator {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
const WebServer *web_server_;
|
const WebServer *web_server_;
|
||||||
#ifdef USE_ARDUINO
|
#ifdef USE_ESP32
|
||||||
DeferredUpdateEventSource *events_;
|
|
||||||
#endif
|
|
||||||
#ifdef USE_ESP_IDF
|
|
||||||
esphome::web_server_idf::AsyncEventSource *events_;
|
esphome::web_server_idf::AsyncEventSource *events_;
|
||||||
|
#elif USE_ARDUINO
|
||||||
|
DeferredUpdateEventSource *events_;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -29,5 +29,5 @@ async def to_code(config):
|
|||||||
await ota_to_code(var, config)
|
await ota_to_code(var, config)
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
cg.add_define("USE_WEBSERVER_OTA")
|
cg.add_define("USE_WEBSERVER_OTA")
|
||||||
if CORE.using_esp_idf:
|
if CORE.is_esp32:
|
||||||
add_idf_component(name="zorxx/multipart-parser", ref="1.0.1")
|
add_idf_component(name="zorxx/multipart-parser", ref="1.0.1")
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user