Compare commits
124 Commits
2025.5.1
...
frame_help
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2968e64d1 | ||
|
|
db9a070c42 | ||
|
|
4bff8ef969 | ||
|
|
9ff93142fd | ||
|
|
a4c5384b30 | ||
|
|
b5af2cb4ee | ||
|
|
29b3d7355c | ||
|
|
2201f67045 | ||
|
|
7a364ff63a | ||
|
|
e544f6711e | ||
|
|
31f5bbf623 | ||
|
|
488dc40f2e | ||
|
|
e8e0e34702 | ||
|
|
eebdc9c38f | ||
|
|
4761ffe023 | ||
|
|
88edddf07a | ||
|
|
0b77cb1d16 | ||
|
|
65b6d256bc | ||
|
|
428371d685 | ||
|
|
d108219947 | ||
|
|
cdf3ed07ba | ||
|
|
061bbabd09 | ||
|
|
2646ec166b | ||
|
|
bafc57f02e | ||
|
|
549ed6178b | ||
|
|
0c67e06573 | ||
|
|
592a95c565 | ||
|
|
f41ef68b41 | ||
|
|
26af1cf650 | ||
|
|
66b995cffe | ||
|
|
5d0b74db3d | ||
|
|
31e3065600 | ||
|
|
872a70d235 | ||
|
|
7b84eb2903 | ||
|
|
7b4e7108c0 | ||
|
|
d4b42ebf20 | ||
|
|
a8e9c79975 | ||
|
|
efa6745a5e | ||
|
|
dd8d8ad952 | ||
|
|
57284b1ac3 | ||
|
|
47b9c3db1d | ||
|
|
b32ed848b0 | ||
|
|
1a651ce66d | ||
|
|
730441c120 | ||
|
|
bb1f24ab43 | ||
|
|
8fb68804e4 | ||
|
|
e2453dd513 | ||
|
|
ed563b0c83 | ||
|
|
856d679ce2 | ||
|
|
f5ac77634b | ||
|
|
b30d7fb0eb | ||
|
|
edb8d187be | ||
|
|
fc609f02f3 | ||
|
|
3fbbec81af | ||
|
|
e7b6081c5c | ||
|
|
7934618c9c | ||
|
|
c4aee545c3 | ||
|
|
5454500024 | ||
|
|
191afd3e69 | ||
|
|
de27ce79dc | ||
|
|
e244b71802 | ||
|
|
2f078d4edf | ||
|
|
a12bd78ceb | ||
|
|
ddb986b4fa | ||
|
|
4215cc5e6a | ||
|
|
b3911ef37c | ||
|
|
0d1dae175c | ||
|
|
6e95ef06e0 | ||
|
|
d7311b048b | ||
|
|
84a84e769b | ||
|
|
0db37ddf0a | ||
|
|
c98c78e368 | ||
|
|
71577cf6d4 | ||
|
|
8c0546b535 | ||
|
|
9bf527b0b6 | ||
|
|
c7501911bf | ||
|
|
4b82ed5b81 | ||
|
|
1f8ae120d4 | ||
|
|
8769ddcfa9 | ||
|
|
3987b98044 | ||
|
|
0edfa4746a | ||
|
|
5570a788fd | ||
|
|
c8dcebfb3f | ||
|
|
edd755323c | ||
|
|
4096c943cc | ||
|
|
e4caef77fc | ||
|
|
0d81306481 | ||
|
|
39abbe609a | ||
|
|
326df5752f | ||
|
|
7cf1db1382 | ||
|
|
dcdc2a30c5 | ||
|
|
c0b9f6407c | ||
|
|
85457eeed0 | ||
|
|
3fb10037a9 | ||
|
|
42c355e6d7 | ||
|
|
a835ab48bc | ||
|
|
f28a373898 | ||
|
|
28e29efd98 | ||
|
|
45e08ed584 | ||
|
|
a7449dce92 | ||
|
|
8067caf16f | ||
|
|
5fbb066ee7 | ||
|
|
c9680a1ccb | ||
|
|
03399e6dd6 | ||
|
|
7f838ece00 | ||
|
|
3f87010c0e | ||
|
|
a960d9966d | ||
|
|
02c390c6c3 | ||
|
|
eebefdf026 | ||
|
|
cb748bbb02 | ||
|
|
c35db19995 | ||
|
|
71b493bd8b | ||
|
|
f67e02c653 | ||
|
|
9db52b17f2 | ||
|
|
d728382542 | ||
|
|
d95bbfc6c4 | ||
|
|
83db3eddd9 | ||
|
|
cc2c5a544e | ||
|
|
8fba8c2800 | ||
|
|
51d1da8460 | ||
|
|
2f1257056d | ||
|
|
2f8f6967bf | ||
|
|
246527e618 | ||
|
|
3857cc9c83 |
4
.github/actions/build-image/action.yaml
vendored
4
.github/actions/build-image/action.yaml
vendored
@@ -47,7 +47,7 @@ runs:
|
||||
|
||||
- name: Build and push to ghcr by digest
|
||||
id: build-ghcr
|
||||
uses: docker/build-push-action@v6.17.0
|
||||
uses: docker/build-push-action@v6.16.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
@@ -73,7 +73,7 @@ runs:
|
||||
|
||||
- name: Build and push to dockerhub by digest
|
||||
id: build-dockerhub
|
||||
uses: docker/build-push-action@v6.17.0
|
||||
uses: docker/build-push-action@v6.16.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
|
||||
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
@@ -18,7 +18,6 @@ jobs:
|
||||
outputs:
|
||||
tag: ${{ steps.tag.outputs.tag }}
|
||||
branch_build: ${{ steps.tag.outputs.branch_build }}
|
||||
deploy_env: ${{ steps.tag.outputs.deploy_env }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.7
|
||||
- name: Get tag
|
||||
@@ -28,11 +27,6 @@ jobs:
|
||||
if [[ "${{ github.event_name }}" = "release" ]]; then
|
||||
TAG="${{ github.event.release.tag_name}}"
|
||||
BRANCH_BUILD="false"
|
||||
if [[ "${{ github.event.release.prerelease }}" = "true" ]]; then
|
||||
ENVIRONMENT="beta"
|
||||
else
|
||||
ENVIRONMENT="production"
|
||||
fi
|
||||
else
|
||||
TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
|
||||
today="$(date --utc '+%Y%m%d')"
|
||||
@@ -41,15 +35,12 @@ jobs:
|
||||
if [[ "$BRANCH" != "dev" ]]; then
|
||||
TAG="${TAG}-${BRANCH}"
|
||||
BRANCH_BUILD="true"
|
||||
ENVIRONMENT=""
|
||||
else
|
||||
BRANCH_BUILD="false"
|
||||
ENVIRONMENT="dev"
|
||||
fi
|
||||
fi
|
||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||
echo "branch_build=${BRANCH_BUILD}" >> $GITHUB_OUTPUT
|
||||
echo "deploy_env=${ENVIRONMENT}" >> $GITHUB_OUTPUT
|
||||
# yamllint enable rule:line-length
|
||||
|
||||
deploy-pypi:
|
||||
@@ -242,8 +233,9 @@ jobs:
|
||||
deploy-esphome-schema:
|
||||
if: github.repository == 'esphome/esphome' && needs.init.outputs.branch_build == 'false'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [init]
|
||||
environment: ${{ needs.init.outputs.deploy_env }}
|
||||
needs:
|
||||
- init
|
||||
- deploy-manifest
|
||||
steps:
|
||||
- name: Trigger Workflow
|
||||
uses: actions/github-script@v7.0.1
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -143,4 +143,3 @@ sdkconfig.*
|
||||
/components
|
||||
/managed_components
|
||||
|
||||
api-docs/
|
||||
|
||||
@@ -11,9 +11,7 @@ FROM base-source-${BUILD_TYPE} AS base
|
||||
|
||||
RUN git config --system --add safe.directory "*"
|
||||
|
||||
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
|
||||
|
||||
RUN pip install --no-cache-dir -U pip uv==0.6.14
|
||||
RUN pip install uv==0.6.14
|
||||
|
||||
COPY requirements.txt /
|
||||
|
||||
|
||||
@@ -432,7 +432,8 @@ message FanCommandRequest {
|
||||
enum ColorMode {
|
||||
COLOR_MODE_UNKNOWN = 0;
|
||||
COLOR_MODE_ON_OFF = 1;
|
||||
COLOR_MODE_BRIGHTNESS = 2;
|
||||
COLOR_MODE_LEGACY_BRIGHTNESS = 2;
|
||||
COLOR_MODE_BRIGHTNESS = 3;
|
||||
COLOR_MODE_WHITE = 7;
|
||||
COLOR_MODE_COLOR_TEMPERATURE = 11;
|
||||
COLOR_MODE_COLD_WARM_WHITE = 19;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <cinttypes>
|
||||
#include <utility>
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -27,6 +26,7 @@ namespace esphome {
|
||||
namespace api {
|
||||
|
||||
static const char *const TAG = "api.connection";
|
||||
static const char *const STATS_TAG = "api.stats";
|
||||
static const int ESP32_CAMERA_STOP_STREAM = 5000;
|
||||
|
||||
// helper for allowing only unique entries in the queue
|
||||
@@ -63,6 +63,11 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
|
||||
: parent_(parent), deferred_message_queue_(this), initial_state_iterator_(this), list_entities_iterator_(this) {
|
||||
this->proto_write_buffer_.reserve(64);
|
||||
|
||||
// Explicitly initialize stats
|
||||
this->stats_enabled_ = true;
|
||||
this->next_stats_log_ = 0;
|
||||
ESP_LOGD(STATS_TAG, "API Connection created with stats_enabled_=true");
|
||||
|
||||
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
|
||||
auto noise_ctx = parent->get_noise_ctx();
|
||||
if (noise_ctx->has_psk()) {
|
||||
@@ -79,11 +84,7 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
|
||||
#endif
|
||||
}
|
||||
void APIConnection::start() {
|
||||
this->last_traffic_ = App.get_loop_component_start_time();
|
||||
|
||||
// Set next_ping_retry_ to prevent immediate ping
|
||||
// This ensures the first ping happens after the keepalive period
|
||||
this->next_ping_retry_ = this->last_traffic_ + KEEPALIVE_TIMEOUT_MS;
|
||||
this->last_traffic_ = millis();
|
||||
|
||||
APIError err = this->helper_->init();
|
||||
if (err != APIError::OK) {
|
||||
@@ -97,6 +98,64 @@ void APIConnection::start() {
|
||||
this->helper_->set_log_info(this->client_info_);
|
||||
}
|
||||
|
||||
void APIConnection::log_section_stats_() {
|
||||
ESP_LOGI(STATS_TAG, "API Connection Section Runtime Statistics");
|
||||
ESP_LOGI(STATS_TAG, "Period stats (last %" PRIu32 "ms):", this->stats_log_interval_);
|
||||
|
||||
if (this->section_stats_.empty()) {
|
||||
ESP_LOGW(STATS_TAG, "No section stats collected yet");
|
||||
return;
|
||||
}
|
||||
|
||||
// First collect stats we want to display
|
||||
std::vector<std::pair<std::string, const APISectionStats *>> stats_to_display;
|
||||
|
||||
for (const auto &it : this->section_stats_) {
|
||||
const APISectionStats &stats = it.second;
|
||||
if (stats.get_period_count() > 0) {
|
||||
stats_to_display.push_back({it.first, &stats});
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by period runtime (descending)
|
||||
std::sort(stats_to_display.begin(), stats_to_display.end(), [](const auto &a, const auto &b) {
|
||||
return a.second->get_period_time_ms() > b.second->get_period_time_ms();
|
||||
});
|
||||
|
||||
// Log top components by period runtime
|
||||
for (const auto &it : stats_to_display) {
|
||||
const std::string §ion = it.first;
|
||||
const APISectionStats *stats = it.second;
|
||||
|
||||
ESP_LOGI(STATS_TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms", section.c_str(),
|
||||
stats->get_period_count(), stats->get_period_avg_time_ms(), stats->get_period_max_time_ms(),
|
||||
stats->get_period_time_ms());
|
||||
}
|
||||
|
||||
// Log total stats since boot
|
||||
ESP_LOGI(STATS_TAG, "Total stats (since boot):");
|
||||
|
||||
// Re-sort by total runtime for all-time stats
|
||||
std::sort(stats_to_display.begin(), stats_to_display.end(),
|
||||
[](const auto &a, const auto &b) { return a.second->get_total_time_ms() > b.second->get_total_time_ms(); });
|
||||
|
||||
for (const auto &it : stats_to_display) {
|
||||
const std::string §ion = it.first;
|
||||
const APISectionStats *stats = it.second;
|
||||
|
||||
ESP_LOGI(STATS_TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms", section.c_str(),
|
||||
stats->get_total_count(), stats->get_total_avg_time_ms(), stats->get_total_max_time_ms(),
|
||||
stats->get_total_time_ms());
|
||||
}
|
||||
}
|
||||
|
||||
void APIConnection::reset_section_stats_() {
|
||||
ESP_LOGD(STATS_TAG, "Resetting API section stats, sections count: %u", this->section_stats_.size());
|
||||
for (auto &it : this->section_stats_) {
|
||||
it.second.reset_period_stats();
|
||||
}
|
||||
}
|
||||
|
||||
APIConnection::~APIConnection() {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
if (bluetooth_proxy::global_bluetooth_proxy->get_api_connection() == this) {
|
||||
@@ -111,6 +170,9 @@ APIConnection::~APIConnection() {
|
||||
}
|
||||
|
||||
void APIConnection::loop() {
|
||||
// Measure total time for entire loop function
|
||||
const uint32_t loop_start_time = millis();
|
||||
|
||||
if (this->remove_)
|
||||
return;
|
||||
|
||||
@@ -128,15 +190,30 @@ void APIConnection::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t now = millis();
|
||||
uint32_t start_time;
|
||||
uint32_t duration;
|
||||
|
||||
// Section: Helper Loop
|
||||
start_time = millis();
|
||||
APIError err = this->helper_->loop();
|
||||
duration = millis() - start_time;
|
||||
this->section_stats_["helper_loop"].record_time(duration);
|
||||
|
||||
if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->client_combined_info_.c_str(),
|
||||
api_error_to_str(err), errno);
|
||||
return;
|
||||
}
|
||||
|
||||
// Section: Read Packet
|
||||
start_time = millis();
|
||||
ReadPacketBuffer buffer;
|
||||
err = this->helper_->read_packet(&buffer);
|
||||
duration = millis() - start_time;
|
||||
this->section_stats_["read_packet"].record_time(duration);
|
||||
|
||||
if (err == APIError::WOULD_BLOCK) {
|
||||
// pass
|
||||
} else if (err != APIError::OK) {
|
||||
@@ -151,36 +228,48 @@ void APIConnection::loop() {
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
this->last_traffic_ = App.get_loop_component_start_time();
|
||||
// read a packet
|
||||
if (buffer.data_len > 0) {
|
||||
this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
|
||||
} else {
|
||||
this->read_message(0, buffer.type, nullptr);
|
||||
}
|
||||
this->last_traffic_ = now;
|
||||
|
||||
// Section: Process Message
|
||||
start_time = millis();
|
||||
this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
|
||||
duration = millis() - start_time;
|
||||
this->section_stats_["process_message"].record_time(duration);
|
||||
|
||||
if (this->remove_)
|
||||
return;
|
||||
}
|
||||
|
||||
// Section: Process Queue
|
||||
start_time = millis();
|
||||
if (!this->deferred_message_queue_.empty() && this->helper_->can_write_without_blocking()) {
|
||||
this->deferred_message_queue_.process_queue();
|
||||
}
|
||||
duration = millis() - start_time;
|
||||
this->section_stats_["process_queue"].record_time(duration);
|
||||
|
||||
// Section: Iterator Advance
|
||||
start_time = millis();
|
||||
if (!this->list_entities_iterator_.completed())
|
||||
this->list_entities_iterator_.advance();
|
||||
if (!this->initial_state_iterator_.completed() && this->list_entities_iterator_.completed())
|
||||
this->initial_state_iterator_.advance();
|
||||
duration = millis() - start_time;
|
||||
this->section_stats_["iterator_advance"].record_time(duration);
|
||||
|
||||
// Section: Keepalive
|
||||
start_time = millis();
|
||||
static uint32_t keepalive = 60000;
|
||||
static uint8_t max_ping_retries = 60;
|
||||
static uint16_t ping_retry_interval = 1000;
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
|
||||
if (this->sent_ping_) {
|
||||
// Disconnect if not responded within 2.5*keepalive
|
||||
if (now - this->last_traffic_ > (KEEPALIVE_TIMEOUT_MS * 5) / 2) {
|
||||
if (now - this->last_traffic_ > (keepalive * 5) / 2) {
|
||||
on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s didn't respond to ping request in time. Disconnecting...", this->client_combined_info_.c_str());
|
||||
}
|
||||
} else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && now > this->next_ping_retry_) {
|
||||
} else if (now - this->last_traffic_ > keepalive && now > this->next_ping_retry_) {
|
||||
ESP_LOGVV(TAG, "Sending keepalive PING...");
|
||||
this->sent_ping_ = this->send_ping_request(PingRequest());
|
||||
if (!this->sent_ping_) {
|
||||
@@ -199,8 +288,12 @@ void APIConnection::loop() {
|
||||
}
|
||||
}
|
||||
}
|
||||
duration = millis() - start_time;
|
||||
this->section_stats_["keepalive"].record_time(duration);
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
// Section: Camera
|
||||
start_time = millis();
|
||||
if (this->image_reader_.available() && this->helper_->can_write_without_blocking()) {
|
||||
// Message will use 8 more bytes than the minimum size, and typical
|
||||
// MTU is 1500. Sometimes users will see as low as 1460 MTU.
|
||||
@@ -239,8 +332,12 @@ void APIConnection::loop() {
|
||||
this->image_reader_.return_image();
|
||||
}
|
||||
}
|
||||
duration = millis() - start_time;
|
||||
this->section_stats_["camera"].record_time(duration);
|
||||
#endif
|
||||
|
||||
// Section: State Subscriptions
|
||||
start_time = millis();
|
||||
if (state_subs_at_ != -1) {
|
||||
const auto &subs = this->parent_->get_state_subs();
|
||||
if (state_subs_at_ >= (int) subs.size()) {
|
||||
@@ -256,6 +353,43 @@ void APIConnection::loop() {
|
||||
}
|
||||
}
|
||||
}
|
||||
duration = millis() - start_time;
|
||||
this->section_stats_["state_subs"].record_time(duration);
|
||||
|
||||
// Log stats periodically
|
||||
if (this->stats_enabled_) {
|
||||
// If next_stats_log_ is 0, initialize it
|
||||
if (this->next_stats_log_ == 0) {
|
||||
this->next_stats_log_ = now + this->stats_log_interval_;
|
||||
ESP_LOGI(STATS_TAG, "API section stats logging enabled, next log at %u", this->next_stats_log_);
|
||||
} else if (now >= this->next_stats_log_) {
|
||||
ESP_LOGI(STATS_TAG, "Logging API section stats now (current time: %u, scheduled time: %u)", now,
|
||||
this->next_stats_log_);
|
||||
// Force logging even if no stats are collected yet
|
||||
ESP_LOGI(STATS_TAG, "Stats collection status: enabled=%d, sections=%u", this->stats_enabled_,
|
||||
this->section_stats_.size());
|
||||
|
||||
// Explicitly log some stats we know should exist
|
||||
ESP_LOGI(STATS_TAG, "Record count for key sections: helper_loop=%u, read_packet=%u, total_loop=%u",
|
||||
this->section_stats_["helper_loop"].get_period_count(),
|
||||
this->section_stats_["read_packet"].get_period_count(),
|
||||
this->section_stats_["total_loop"].get_period_count());
|
||||
|
||||
this->log_section_stats_();
|
||||
this->reset_section_stats_();
|
||||
this->next_stats_log_ = now + this->stats_log_interval_;
|
||||
ESP_LOGI(STATS_TAG, "Next API section stats log scheduled for %u", this->next_stats_log_);
|
||||
}
|
||||
}
|
||||
|
||||
// Record total loop execution time
|
||||
const uint32_t total_loop_duration = millis() - loop_start_time;
|
||||
this->section_stats_["total_loop"].record_time(total_loop_duration);
|
||||
|
||||
// Log a warning if the loop takes longer than 30ms
|
||||
if (total_loop_duration > 30) {
|
||||
ESP_LOGW(STATS_TAG, "API loop took %ums, which exceeds the recommended 30ms limit", total_loop_duration);
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_default_unique_id(const std::string &component_type, EntityBase *entity) {
|
||||
@@ -1648,11 +1782,24 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
|
||||
return false;
|
||||
}
|
||||
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) {
|
||||
if (!this->try_to_clear_buffer(message_type != 29)) { // SubscribeLogsResponse
|
||||
// Track send_buffer time
|
||||
const uint32_t start_time = millis();
|
||||
|
||||
if (this->remove_)
|
||||
return false;
|
||||
|
||||
uint32_t check_block_start = millis();
|
||||
if (!this->try_to_clear_buffer(true)) {
|
||||
return false;
|
||||
}
|
||||
uint32_t check_block_duration = millis() - check_block_start;
|
||||
this->section_stats_["try_to_clear_buffer"].record_time(check_block_duration);
|
||||
|
||||
uint32_t write_start = millis();
|
||||
APIError err = this->helper_->write_packet(message_type, buffer.get_buffer()->data(), buffer.get_buffer()->size());
|
||||
uint32_t write_duration = millis() - write_start;
|
||||
this->section_stats_["write_packet"].record_time(write_duration);
|
||||
|
||||
APIError err = this->helper_->write_protobuf_packet(message_type, buffer);
|
||||
if (err == APIError::WOULD_BLOCK)
|
||||
return false;
|
||||
if (err != APIError::OK) {
|
||||
@@ -1665,6 +1812,17 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Measure total send_buffer function time
|
||||
uint32_t total_duration = millis() - start_time;
|
||||
this->section_stats_["send_buffer_total"].record_time(total_duration);
|
||||
|
||||
// Log a warning if send_buffer takes longer than 15ms
|
||||
if (total_duration > 15) {
|
||||
ESP_LOGW(STATS_TAG, "send_buffer took %ums (message_type=%u, size=%u)", total_duration, message_type,
|
||||
buffer.get_buffer()->size());
|
||||
}
|
||||
|
||||
// Do not set last_traffic_ on send
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -11,13 +11,14 @@
|
||||
#include "esphome/core/entity_base.h"
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
// Keepalive timeout in milliseconds
|
||||
static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000;
|
||||
|
||||
using send_message_t = bool (APIConnection::*)(void *);
|
||||
|
||||
/*
|
||||
@@ -64,6 +65,70 @@ class APIConnection : public APIServerConnection {
|
||||
APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent);
|
||||
virtual ~APIConnection();
|
||||
|
||||
// Performance statistics class for API loop sections
|
||||
class APISectionStats {
|
||||
public:
|
||||
APISectionStats()
|
||||
: period_count_(0),
|
||||
total_count_(0),
|
||||
period_time_ms_(0),
|
||||
total_time_ms_(0),
|
||||
period_max_time_ms_(0),
|
||||
total_max_time_ms_(0) {}
|
||||
|
||||
void record_time(uint32_t duration_ms) {
|
||||
// Update period counters
|
||||
this->period_count_++;
|
||||
this->period_time_ms_ += duration_ms;
|
||||
if (duration_ms > this->period_max_time_ms_)
|
||||
this->period_max_time_ms_ = duration_ms;
|
||||
|
||||
// Update total counters
|
||||
this->total_count_++;
|
||||
this->total_time_ms_ += duration_ms;
|
||||
if (duration_ms > this->total_max_time_ms_)
|
||||
this->total_max_time_ms_ = duration_ms;
|
||||
|
||||
// Log if this is the first record in this period
|
||||
if (this->period_count_ == 1) {
|
||||
ESP_LOGV("api.stats", "First time recording stats for this section: %u ms", duration_ms);
|
||||
}
|
||||
}
|
||||
|
||||
void reset_period_stats() {
|
||||
this->period_count_ = 0;
|
||||
this->period_time_ms_ = 0;
|
||||
this->period_max_time_ms_ = 0;
|
||||
}
|
||||
|
||||
// Period stats (reset each logging interval)
|
||||
uint32_t get_period_count() const { return this->period_count_; }
|
||||
uint32_t get_period_time_ms() const { return this->period_time_ms_; }
|
||||
uint32_t get_period_max_time_ms() const { return this->period_max_time_ms_; }
|
||||
float get_period_avg_time_ms() const {
|
||||
return this->period_count_ > 0 ? this->period_time_ms_ / static_cast<float>(this->period_count_) : 0.0f;
|
||||
}
|
||||
|
||||
// Total stats (persistent until reboot)
|
||||
uint32_t get_total_count() const { return this->total_count_; }
|
||||
uint32_t get_total_time_ms() const { return this->total_time_ms_; }
|
||||
uint32_t get_total_max_time_ms() const { return this->total_max_time_ms_; }
|
||||
float get_total_avg_time_ms() const {
|
||||
return this->total_count_ > 0 ? this->total_time_ms_ / static_cast<float>(this->total_count_) : 0.0f;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Period stats (reset each logging interval)
|
||||
uint32_t period_count_;
|
||||
uint32_t period_time_ms_;
|
||||
uint32_t period_max_time_ms_;
|
||||
|
||||
// Total stats (persistent until reboot)
|
||||
uint32_t total_count_;
|
||||
uint32_t total_time_ms_;
|
||||
uint32_t total_max_time_ms_;
|
||||
};
|
||||
|
||||
void start();
|
||||
void loop();
|
||||
|
||||
@@ -408,14 +473,7 @@ class APIConnection : public APIServerConnection {
|
||||
ProtoWriteBuffer create_buffer(uint32_t reserve_size) override {
|
||||
// FIXME: ensure no recursive writes can happen
|
||||
this->proto_write_buffer_.clear();
|
||||
// Get header padding size - used for both reserve and insert
|
||||
uint8_t header_padding = this->helper_->frame_header_padding();
|
||||
// Reserve space for header padding + message + footer
|
||||
// - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext)
|
||||
// - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext)
|
||||
this->proto_write_buffer_.reserve(reserve_size + header_padding + this->helper_->frame_footer_size());
|
||||
// Insert header padding bytes so message encoding starts at the correct position
|
||||
this->proto_write_buffer_.insert(this->proto_write_buffer_.begin(), header_padding, 0);
|
||||
this->proto_write_buffer_.reserve(reserve_size);
|
||||
return {&this->proto_write_buffer_};
|
||||
}
|
||||
bool try_to_clear_buffer(bool log_out_of_space);
|
||||
@@ -556,6 +614,17 @@ class APIConnection : public APIServerConnection {
|
||||
InitialStateIterator initial_state_iterator_;
|
||||
ListEntitiesIterator list_entities_iterator_;
|
||||
int state_subs_at_ = -1;
|
||||
|
||||
// API loop section performance statistics
|
||||
std::map<std::string, APISectionStats> section_stats_;
|
||||
uint32_t stats_log_interval_{60000}; // 60 seconds default
|
||||
uint32_t next_stats_log_{0};
|
||||
bool stats_enabled_{true};
|
||||
void log_section_stats_();
|
||||
void reset_section_stats_();
|
||||
|
||||
// Method to enable/disable section stats
|
||||
void set_stats_enabled(bool enabled) { this->stats_enabled_ = enabled; }
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
||||
@@ -13,14 +13,6 @@ namespace api {
|
||||
|
||||
static const char *const TAG = "api.socket";
|
||||
|
||||
/// Is the given return value (from write syscalls) a wouldblock error?
|
||||
bool is_would_block(ssize_t ret) {
|
||||
if (ret == -1) {
|
||||
return errno == EWOULDBLOCK || errno == EAGAIN;
|
||||
}
|
||||
return ret == 0;
|
||||
}
|
||||
|
||||
const char *api_error_to_str(APIError err) {
|
||||
// not using switch to ensure compiler doesn't try to build a big table out of it
|
||||
if (err == APIError::OK) {
|
||||
@@ -73,14 +65,21 @@ const char *api_error_to_str(APIError err) {
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
// Common implementation for writing raw data to socket
|
||||
template<typename StateEnum>
|
||||
APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket,
|
||||
std::vector<uint8_t> &tx_buf, const std::string &info, StateEnum &state,
|
||||
StateEnum failed_state) {
|
||||
// This method writes data to socket or buffers it
|
||||
// Helper method to buffer data from IOVs
|
||||
void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, size_t total_write_len) {
|
||||
SendBuffer buffer;
|
||||
buffer.data.reserve(total_write_len);
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
const uint8_t *data = reinterpret_cast<uint8_t *>(iov[i].iov_base);
|
||||
buffer.data.insert(buffer.data.end(), data, data + iov[i].iov_len);
|
||||
}
|
||||
this->tx_buf_.push_back(std::move(buffer));
|
||||
}
|
||||
|
||||
// This method writes data to socket or buffers it
|
||||
APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
|
||||
// Returns APIError::OK if successful (or would block, but data has been buffered)
|
||||
// Returns APIError::SOCKET_WRITE_FAILED if socket write failed, and sets state to failed_state
|
||||
// Returns APIError::SOCKET_WRITE_FAILED if socket write failed, and sets state to FAILED
|
||||
|
||||
if (iovcnt == 0)
|
||||
return APIError::OK; // Nothing to do, success
|
||||
@@ -94,71 +93,104 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, socket:
|
||||
total_write_len += iov[i].iov_len;
|
||||
}
|
||||
|
||||
if (!tx_buf.empty()) {
|
||||
// try to empty tx_buf first
|
||||
while (!tx_buf.empty()) {
|
||||
ssize_t sent = socket->write(tx_buf.data(), tx_buf.size());
|
||||
if (is_would_block(sent)) {
|
||||
break;
|
||||
} else if (sent == -1) {
|
||||
ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", info.c_str(), errno);
|
||||
state = failed_state;
|
||||
return APIError::SOCKET_WRITE_FAILED; // Socket write failed
|
||||
}
|
||||
// TODO: inefficient if multiple packets in txbuf
|
||||
// replace with deque of buffers
|
||||
tx_buf.erase(tx_buf.begin(), tx_buf.begin() + sent);
|
||||
// Try to send any existing buffered data first if there is any
|
||||
if (!this->tx_buf_.empty()) {
|
||||
APIError send_result = try_send_tx_buf_();
|
||||
// If real error occurred (not just WOULD_BLOCK), return it
|
||||
if (send_result != APIError::OK && send_result != APIError::WOULD_BLOCK) {
|
||||
return send_result;
|
||||
}
|
||||
|
||||
// If there is still data in the buffer, we can't send, buffer
|
||||
// the new data and return
|
||||
if (!this->tx_buf_.empty()) {
|
||||
this->buffer_data_from_iov_(iov, iovcnt, total_write_len);
|
||||
return APIError::OK; // Success, data buffered
|
||||
}
|
||||
}
|
||||
|
||||
if (!tx_buf.empty()) {
|
||||
// tx buf not empty, can't write now because then stream would be inconsistent
|
||||
// Reserve space upfront to avoid multiple reallocations
|
||||
tx_buf.reserve(tx_buf.size() + total_write_len);
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
|
||||
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
|
||||
}
|
||||
return APIError::OK; // Success, data buffered
|
||||
}
|
||||
// Try to send directly if no buffered data
|
||||
ssize_t sent = this->socket_->writev(iov, iovcnt);
|
||||
|
||||
ssize_t sent = socket->writev(iov, iovcnt);
|
||||
if (is_would_block(sent)) {
|
||||
// operation would block, add buffer to tx_buf
|
||||
// Reserve space upfront to avoid multiple reallocations
|
||||
tx_buf.reserve(tx_buf.size() + total_write_len);
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
|
||||
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
|
||||
if (sent == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
// Socket would block, buffer the data
|
||||
this->buffer_data_from_iov_(iov, iovcnt, total_write_len);
|
||||
return APIError::OK; // Success, data buffered
|
||||
}
|
||||
return APIError::OK; // Success, data buffered
|
||||
} else if (sent == -1) {
|
||||
// an error occurred
|
||||
ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", info.c_str(), errno);
|
||||
state = failed_state;
|
||||
// Socket error
|
||||
ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", this->info_.c_str(), errno);
|
||||
this->state_ = State::FAILED;
|
||||
return APIError::SOCKET_WRITE_FAILED; // Socket write failed
|
||||
} else if ((size_t) sent != total_write_len) {
|
||||
// partially sent, add end to tx_buf
|
||||
size_t remaining = total_write_len - sent;
|
||||
// Reserve space upfront to avoid multiple reallocations
|
||||
tx_buf.reserve(tx_buf.size() + remaining);
|
||||
|
||||
} else if (static_cast<size_t>(sent) < total_write_len) {
|
||||
// Partially sent, buffer the remaining data
|
||||
SendBuffer buffer;
|
||||
size_t to_consume = sent;
|
||||
size_t remaining = total_write_len - sent;
|
||||
|
||||
buffer.data.reserve(remaining);
|
||||
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
if (to_consume >= iov[i].iov_len) {
|
||||
// This segment was fully sent
|
||||
to_consume -= iov[i].iov_len;
|
||||
} else {
|
||||
tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
|
||||
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
|
||||
// This segment was partially sent or not sent at all
|
||||
const uint8_t *data = reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume;
|
||||
size_t len = iov[i].iov_len - to_consume;
|
||||
buffer.data.insert(buffer.data.end(), data, data + len);
|
||||
to_consume = 0;
|
||||
}
|
||||
}
|
||||
return APIError::OK; // Success, data buffered
|
||||
|
||||
this->tx_buf_.push_back(std::move(buffer));
|
||||
}
|
||||
return APIError::OK; // Success, all data sent
|
||||
|
||||
return APIError::OK; // Success, all data sent or buffered
|
||||
}
|
||||
|
||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__)
|
||||
// Common implementation for trying to send buffered data
|
||||
// IMPORTANT: Caller MUST ensure tx_buf_ is not empty before calling this method
|
||||
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
|
||||
bool tx_buf_empty = false;
|
||||
while (!tx_buf_empty) {
|
||||
// Get the first buffer in the queue
|
||||
SendBuffer &front_buffer = this->tx_buf_.front();
|
||||
|
||||
// Try to send the remaining data in this buffer
|
||||
ssize_t sent = this->socket_->write(front_buffer.current_data(), front_buffer.remaining());
|
||||
|
||||
if (sent == -1) {
|
||||
if (errno != EWOULDBLOCK && errno != EAGAIN) {
|
||||
// Real socket error (not just would block)
|
||||
ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", this->info_.c_str(), errno);
|
||||
this->state_ = State::FAILED;
|
||||
return APIError::SOCKET_WRITE_FAILED; // Socket write failed
|
||||
}
|
||||
// Socket would block, we'll try again later
|
||||
return APIError::WOULD_BLOCK;
|
||||
} else if (sent == 0) {
|
||||
// Nothing sent but not an error
|
||||
return APIError::WOULD_BLOCK;
|
||||
} else if (static_cast<uint16_t>(sent) < front_buffer.remaining()) {
|
||||
// Partially sent, update offset
|
||||
// Cast to ensure no overflow issues with uint16_t
|
||||
front_buffer.offset += static_cast<uint16_t>(sent);
|
||||
return APIError::WOULD_BLOCK; // Stop processing more buffers if we couldn't send a complete buffer
|
||||
} else {
|
||||
// Buffer completely sent, remove it from the queue
|
||||
this->tx_buf_.pop_front();
|
||||
// Update empty status for the loop condition
|
||||
tx_buf_empty = this->tx_buf_.empty();
|
||||
// Continue loop to try sending the next buffer
|
||||
}
|
||||
}
|
||||
|
||||
return APIError::OK; // All buffers sent successfully
|
||||
}
|
||||
|
||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->info_.c_str(), ##__VA_ARGS__)
|
||||
// uncomment to log raw packets
|
||||
//#define HELPER_LOG_PACKETS
|
||||
|
||||
@@ -206,11 +238,11 @@ std::string noise_err_to_str(int err) {
|
||||
|
||||
/// Initialize the frame helper, returns OK if successful.
|
||||
APIError APINoiseFrameHelper::init() {
|
||||
if (state_ != State::INITIALIZE || socket_ == nullptr) {
|
||||
if (state_ != State::INITIALIZE || this->socket_ == nullptr) {
|
||||
HELPER_LOG("Bad state for init %d", (int) state_);
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
int err = socket_->setblocking(false);
|
||||
int err = this->socket_->setblocking(false);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Setting nonblocking failed with errno %d", errno);
|
||||
@@ -218,7 +250,7 @@ APIError APINoiseFrameHelper::init() {
|
||||
}
|
||||
|
||||
int enable = 1;
|
||||
err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
|
||||
err = this->socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Setting nodelay failed with errno %d", errno);
|
||||
@@ -238,13 +270,12 @@ APIError APINoiseFrameHelper::loop() {
|
||||
return APIError::OK;
|
||||
if (err != APIError::OK)
|
||||
return err;
|
||||
if (!tx_buf_.empty()) {
|
||||
err = try_send_tx_buf_();
|
||||
if (err != APIError::OK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return APIError::OK;
|
||||
if (this->tx_buf_.empty())
|
||||
return APIError::OK;
|
||||
err = try_send_tx_buf_();
|
||||
if (err == APIError::WOULD_BLOCK)
|
||||
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
|
||||
return err;
|
||||
}
|
||||
|
||||
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
|
||||
@@ -271,7 +302,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
if (rx_header_buf_len_ < 3) {
|
||||
// no header information yet
|
||||
size_t to_read = 3 - rx_header_buf_len_;
|
||||
ssize_t received = socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
|
||||
ssize_t received = this->socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
|
||||
if (received == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
@@ -318,7 +349,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
if (rx_buf_len_ < msg_size) {
|
||||
// more data to read
|
||||
size_t to_read = msg_size - rx_buf_len_;
|
||||
ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read);
|
||||
ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
|
||||
if (received == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
@@ -493,12 +524,9 @@ void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &rea
|
||||
std::vector<uint8_t> data;
|
||||
data.resize(reason.length() + 1);
|
||||
data[0] = 0x01; // failure
|
||||
|
||||
// Copy error message in bulk
|
||||
if (!reason.empty()) {
|
||||
std::memcpy(data.data() + 1, reason.c_str(), reason.length());
|
||||
for (size_t i = 0; i < reason.length(); i++) {
|
||||
data[i + 1] = (uint8_t) reason[i];
|
||||
}
|
||||
|
||||
// temporarily remove failed state
|
||||
auto orig_state = state_;
|
||||
state_ = State::EXPLICIT_REJECT;
|
||||
@@ -559,8 +587,7 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
buffer->type = type;
|
||||
return APIError::OK;
|
||||
}
|
||||
bool APINoiseFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
|
||||
APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
|
||||
APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
|
||||
int err;
|
||||
APIError aerr;
|
||||
aerr = state_action_();
|
||||
@@ -572,36 +599,31 @@ APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuf
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
||||
// Message data starts after padding
|
||||
size_t payload_len = raw_buffer->size() - frame_header_padding_;
|
||||
size_t padding = 0;
|
||||
size_t msg_len = 4 + payload_len + padding;
|
||||
size_t frame_len = 3 + msg_len + noise_cipherstate_get_mac_length(send_cipher_);
|
||||
auto tmpbuf = std::unique_ptr<uint8_t[]>{new (std::nothrow) uint8_t[frame_len]};
|
||||
if (tmpbuf == nullptr) {
|
||||
HELPER_LOG("Could not allocate for writing packet");
|
||||
return APIError::OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// We need to resize to include MAC space, but we already reserved it in create_buffer
|
||||
raw_buffer->resize(raw_buffer->size() + frame_footer_size_);
|
||||
|
||||
// Write the noise header in the padded area
|
||||
// Buffer layout:
|
||||
// [0] - 0x01 indicator byte
|
||||
// [1-2] - Size of encrypted payload (filled after encryption)
|
||||
// [3-4] - Message type (encrypted)
|
||||
// [5-6] - Payload length (encrypted)
|
||||
// [7...] - Actual payload data (encrypted)
|
||||
uint8_t *buf_start = raw_buffer->data();
|
||||
buf_start[0] = 0x01; // indicator
|
||||
// buf_start[1], buf_start[2] to be set later after encryption
|
||||
tmpbuf[0] = 0x01; // indicator
|
||||
// tmpbuf[1], tmpbuf[2] to be set later
|
||||
const uint8_t msg_offset = 3;
|
||||
buf_start[msg_offset + 0] = (uint8_t) (type >> 8); // type high byte
|
||||
buf_start[msg_offset + 1] = (uint8_t) type; // type low byte
|
||||
buf_start[msg_offset + 2] = (uint8_t) (payload_len >> 8); // data_len high byte
|
||||
buf_start[msg_offset + 3] = (uint8_t) payload_len; // data_len low byte
|
||||
// payload data is already in the buffer starting at position 7
|
||||
const uint8_t payload_offset = msg_offset + 4;
|
||||
tmpbuf[msg_offset + 0] = (uint8_t) (type >> 8); // type
|
||||
tmpbuf[msg_offset + 1] = (uint8_t) type;
|
||||
tmpbuf[msg_offset + 2] = (uint8_t) (payload_len >> 8); // data_len
|
||||
tmpbuf[msg_offset + 3] = (uint8_t) payload_len;
|
||||
// copy data
|
||||
std::copy(payload, payload + payload_len, &tmpbuf[payload_offset]);
|
||||
// fill padding with zeros
|
||||
std::fill(&tmpbuf[payload_offset + payload_len], &tmpbuf[frame_len], 0);
|
||||
|
||||
NoiseBuffer mbuf;
|
||||
noise_buffer_init(mbuf);
|
||||
// The capacity parameter should be msg_len + frame_footer_size_ (MAC length) to allow space for encryption
|
||||
noise_buffer_set_inout(mbuf, buf_start + msg_offset, msg_len, msg_len + frame_footer_size_);
|
||||
noise_buffer_set_inout(mbuf, &tmpbuf[msg_offset], msg_len, frame_len - msg_offset);
|
||||
err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
@@ -610,37 +632,15 @@ APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuf
|
||||
}
|
||||
|
||||
size_t total_len = 3 + mbuf.size;
|
||||
buf_start[1] = (uint8_t) (mbuf.size >> 8);
|
||||
buf_start[2] = (uint8_t) mbuf.size;
|
||||
tmpbuf[1] = (uint8_t) (mbuf.size >> 8);
|
||||
tmpbuf[2] = (uint8_t) mbuf.size;
|
||||
|
||||
struct iovec iov;
|
||||
// Point iov_base to the beginning of the buffer (no unused padding in Noise)
|
||||
// We send the entire frame: indicator + size + encrypted(type + data_len + payload + MAC)
|
||||
iov.iov_base = buf_start;
|
||||
iov.iov_base = &tmpbuf[0];
|
||||
iov.iov_len = total_len;
|
||||
|
||||
// write raw to not have two packets sent if NAGLE disabled
|
||||
return write_raw_(&iov, 1);
|
||||
}
|
||||
APIError APINoiseFrameHelper::try_send_tx_buf_() {
|
||||
// try send from tx_buf
|
||||
while (state_ != State::CLOSED && !tx_buf_.empty()) {
|
||||
ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size());
|
||||
if (sent == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN)
|
||||
break;
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket write failed with errno %d", errno);
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
} else if (sent == 0) {
|
||||
break;
|
||||
}
|
||||
// TODO: inefficient if multiple packets in txbuf
|
||||
// replace with deque of buffers
|
||||
tx_buf_.erase(tx_buf_.begin(), tx_buf_.begin() + sent);
|
||||
}
|
||||
|
||||
return APIError::OK;
|
||||
return APIFrameHelper::write_raw_(&iov, 1);
|
||||
}
|
||||
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
|
||||
uint8_t header[3];
|
||||
@@ -652,12 +652,12 @@ APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
|
||||
iov[0].iov_base = header;
|
||||
iov[0].iov_len = 3;
|
||||
if (len == 0) {
|
||||
return write_raw_(iov, 1);
|
||||
return APIFrameHelper::write_raw_(iov, 1);
|
||||
}
|
||||
iov[1].iov_base = const_cast<uint8_t *>(data);
|
||||
iov[1].iov_len = len;
|
||||
|
||||
return write_raw_(iov, 2);
|
||||
return APIFrameHelper::write_raw_(iov, 2);
|
||||
}
|
||||
|
||||
/** Initiate the data structures for the handshake.
|
||||
@@ -728,8 +728,6 @@ APIError APINoiseFrameHelper::check_handshake_finished_() {
|
||||
return APIError::HANDSHAKESTATE_SPLIT_FAILED;
|
||||
}
|
||||
|
||||
frame_footer_size_ = noise_cipherstate_get_mac_length(send_cipher_);
|
||||
|
||||
HELPER_LOG("Handshake complete!");
|
||||
noise_handshakestate_free(handshake_);
|
||||
handshake_ = nullptr;
|
||||
@@ -752,22 +750,6 @@ APINoiseFrameHelper::~APINoiseFrameHelper() {
|
||||
}
|
||||
}
|
||||
|
||||
APIError APINoiseFrameHelper::close() {
|
||||
state_ = State::CLOSED;
|
||||
int err = socket_->close();
|
||||
if (err == -1)
|
||||
return APIError::CLOSE_FAILED;
|
||||
return APIError::OK;
|
||||
}
|
||||
APIError APINoiseFrameHelper::shutdown(int how) {
|
||||
int err = socket_->shutdown(how);
|
||||
if (err == -1)
|
||||
return APIError::SHUTDOWN_FAILED;
|
||||
if (how == SHUT_RDWR) {
|
||||
state_ = State::CLOSED;
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
extern "C" {
|
||||
// declare how noise generates random bytes (here with a good HWRNG based on the RF system)
|
||||
void noise_rand_bytes(void *output, size_t len) {
|
||||
@@ -778,28 +760,24 @@ void noise_rand_bytes(void *output, size_t len) {
|
||||
}
|
||||
}
|
||||
|
||||
// Explicit template instantiation for Noise
|
||||
template APIError APIFrameHelper::write_raw_<APINoiseFrameHelper::State>(
|
||||
const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf_, const std::string &info,
|
||||
APINoiseFrameHelper::State &state, APINoiseFrameHelper::State failed_state);
|
||||
#endif // USE_API_NOISE
|
||||
|
||||
#ifdef USE_API_PLAINTEXT
|
||||
|
||||
/// Initialize the frame helper, returns OK if successful.
|
||||
APIError APIPlaintextFrameHelper::init() {
|
||||
if (state_ != State::INITIALIZE || socket_ == nullptr) {
|
||||
if (state_ != State::INITIALIZE || this->socket_ == nullptr) {
|
||||
HELPER_LOG("Bad state for init %d", (int) state_);
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
int err = socket_->setblocking(false);
|
||||
int err = this->socket_->setblocking(false);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Setting nonblocking failed with errno %d", errno);
|
||||
return APIError::TCP_NONBLOCKING_FAILED;
|
||||
}
|
||||
int enable = 1;
|
||||
err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
|
||||
err = this->socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Setting nodelay failed with errno %d", errno);
|
||||
@@ -814,14 +792,12 @@ APIError APIPlaintextFrameHelper::loop() {
|
||||
if (state_ != State::DATA) {
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
// try send pending TX data
|
||||
if (!tx_buf_.empty()) {
|
||||
APIError err = try_send_tx_buf_();
|
||||
if (err != APIError::OK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return APIError::OK;
|
||||
if (this->tx_buf_.empty())
|
||||
return APIError::OK;
|
||||
APIError err = try_send_tx_buf_();
|
||||
if (err == APIError::WOULD_BLOCK)
|
||||
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
|
||||
return err;
|
||||
}
|
||||
|
||||
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
|
||||
@@ -930,7 +906,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
if (rx_buf_len_ < rx_header_parsed_len_) {
|
||||
// more data to read
|
||||
size_t to_read = rx_header_parsed_len_ - rx_buf_len_;
|
||||
ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read);
|
||||
ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
|
||||
if (received == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
@@ -990,7 +966,7 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
"Bad indicator byte";
|
||||
iov[0].iov_base = (void *) msg;
|
||||
iov[0].iov_len = 19;
|
||||
write_raw_(iov, 1);
|
||||
APIFrameHelper::write_raw_(iov, 1);
|
||||
}
|
||||
return aerr;
|
||||
}
|
||||
@@ -1001,108 +977,30 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
buffer->type = rx_header_parsed_type_;
|
||||
return APIError::OK;
|
||||
}
|
||||
bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
|
||||
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
|
||||
APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
|
||||
if (state_ != State::DATA) {
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
||||
// Message data starts after padding (frame_header_padding_ = 6)
|
||||
size_t payload_len = raw_buffer->size() - frame_header_padding_;
|
||||
std::vector<uint8_t> header;
|
||||
header.reserve(1 + api::ProtoSize::varint(static_cast<uint32_t>(payload_len)) +
|
||||
api::ProtoSize::varint(static_cast<uint32_t>(type)));
|
||||
header.push_back(0x00);
|
||||
ProtoVarInt(payload_len).encode(header);
|
||||
ProtoVarInt(type).encode(header);
|
||||
|
||||
// Calculate varint sizes for header components
|
||||
size_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(payload_len));
|
||||
size_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(type));
|
||||
size_t total_header_len = 1 + size_varint_len + type_varint_len;
|
||||
|
||||
if (total_header_len > frame_header_padding_) {
|
||||
// Header is too large to fit in the padding
|
||||
return APIError::BAD_ARG;
|
||||
struct iovec iov[2];
|
||||
iov[0].iov_base = &header[0];
|
||||
iov[0].iov_len = header.size();
|
||||
if (payload_len == 0) {
|
||||
return APIFrameHelper::write_raw_(iov, 1);
|
||||
}
|
||||
iov[1].iov_base = const_cast<uint8_t *>(payload);
|
||||
iov[1].iov_len = payload_len;
|
||||
|
||||
// Calculate where to start writing the header
|
||||
// The header starts at the latest possible position to minimize unused padding
|
||||
//
|
||||
// Example 1 (small values): total_header_len = 3, header_offset = 6 - 3 = 3
|
||||
// [0-2] - Unused padding
|
||||
// [3] - 0x00 indicator byte
|
||||
// [4] - Payload size varint (1 byte, for sizes 0-127)
|
||||
// [5] - Message type varint (1 byte, for types 0-127)
|
||||
// [6...] - Actual payload data
|
||||
//
|
||||
// Example 2 (medium values): total_header_len = 4, header_offset = 6 - 4 = 2
|
||||
// [0-1] - Unused padding
|
||||
// [2] - 0x00 indicator byte
|
||||
// [3-4] - Payload size varint (2 bytes, for sizes 128-16383)
|
||||
// [5] - Message type varint (1 byte, for types 0-127)
|
||||
// [6...] - Actual payload data
|
||||
//
|
||||
// Example 3 (large values): total_header_len = 6, header_offset = 6 - 6 = 0
|
||||
// [0] - 0x00 indicator byte
|
||||
// [1-3] - Payload size varint (3 bytes, for sizes 16384-2097151)
|
||||
// [4-5] - Message type varint (2 bytes, for types 128-32767)
|
||||
// [6...] - Actual payload data
|
||||
uint8_t *buf_start = raw_buffer->data();
|
||||
size_t header_offset = frame_header_padding_ - total_header_len;
|
||||
|
||||
// Write the plaintext header
|
||||
buf_start[header_offset] = 0x00; // indicator
|
||||
|
||||
// Encode size varint directly into buffer
|
||||
ProtoVarInt(payload_len).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
|
||||
|
||||
// Encode type varint directly into buffer
|
||||
ProtoVarInt(type).encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
|
||||
|
||||
struct iovec iov;
|
||||
// Point iov_base to the beginning of our header (skip unused padding)
|
||||
// This ensures we only send the actual header and payload, not the empty padding bytes
|
||||
iov.iov_base = buf_start + header_offset;
|
||||
iov.iov_len = total_header_len + payload_len;
|
||||
|
||||
return write_raw_(&iov, 1);
|
||||
}
|
||||
APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
|
||||
// try send from tx_buf
|
||||
while (state_ != State::CLOSED && !tx_buf_.empty()) {
|
||||
ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size());
|
||||
if (is_would_block(sent)) {
|
||||
break;
|
||||
} else if (sent == -1) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket write failed with errno %d", errno);
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
}
|
||||
// TODO: inefficient if multiple packets in txbuf
|
||||
// replace with deque of buffers
|
||||
tx_buf_.erase(tx_buf_.begin(), tx_buf_.begin() + sent);
|
||||
}
|
||||
|
||||
return APIError::OK;
|
||||
return APIFrameHelper::write_raw_(iov, 2);
|
||||
}
|
||||
|
||||
APIError APIPlaintextFrameHelper::close() {
|
||||
state_ = State::CLOSED;
|
||||
int err = socket_->close();
|
||||
if (err == -1)
|
||||
return APIError::CLOSE_FAILED;
|
||||
return APIError::OK;
|
||||
}
|
||||
APIError APIPlaintextFrameHelper::shutdown(int how) {
|
||||
int err = socket_->shutdown(how);
|
||||
if (err == -1)
|
||||
return APIError::SHUTDOWN_FAILED;
|
||||
if (how == SHUT_RDWR) {
|
||||
state_ = State::CLOSED;
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
// Explicit template instantiation for Plaintext
|
||||
template APIError APIFrameHelper::write_raw_<APIPlaintextFrameHelper::State>(
|
||||
const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf_, const std::string &info,
|
||||
APIPlaintextFrameHelper::State &state, APIPlaintextFrameHelper::State failed_state);
|
||||
#endif // USE_API_PLAINTEXT
|
||||
|
||||
} // namespace api
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class ProtoWriteBuffer;
|
||||
|
||||
struct ReadPacketBuffer {
|
||||
std::vector<uint8_t> container;
|
||||
uint16_t type;
|
||||
@@ -62,83 +60,134 @@ const char *api_error_to_str(APIError err);
|
||||
|
||||
class APIFrameHelper {
|
||||
public:
|
||||
APIFrameHelper() = default;
|
||||
explicit APIFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_owned_(std::move(socket)) {
|
||||
socket_ = socket_owned_.get();
|
||||
}
|
||||
virtual ~APIFrameHelper() = default;
|
||||
virtual APIError init() = 0;
|
||||
virtual APIError loop() = 0;
|
||||
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
|
||||
virtual bool can_write_without_blocking() = 0;
|
||||
virtual APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) = 0;
|
||||
virtual std::string getpeername() = 0;
|
||||
virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0;
|
||||
virtual APIError close() = 0;
|
||||
virtual APIError shutdown(int how) = 0;
|
||||
bool can_write_without_blocking() {
|
||||
// First check if we're in the DATA state
|
||||
if (state_ != State::DATA) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Empty buffer can always accept more data
|
||||
if (tx_buf_.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Optimization: Allow writing even with a small buffer backlog to reduce delays in message processing.
|
||||
// This improves throughput for real-time data like sensor readings and prevents high-priority
|
||||
// messages from being unnecessarily delayed by a small queue backlog.
|
||||
// The 256-byte threshold is small enough to not impact memory usage significantly
|
||||
// but large enough to improve overall system responsiveness.
|
||||
if (tx_buf_.size() == 1 && tx_buf_.front().remaining() < 256) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
virtual APIError write_packet(uint16_t type, const uint8_t *data, size_t len) = 0;
|
||||
std::string getpeername() { return socket_->getpeername(); }
|
||||
int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); }
|
||||
APIError close() {
|
||||
state_ = State::CLOSED;
|
||||
int err = this->socket_->close();
|
||||
if (err == -1)
|
||||
return APIError::CLOSE_FAILED;
|
||||
return APIError::OK;
|
||||
}
|
||||
APIError shutdown(int how) {
|
||||
int err = this->socket_->shutdown(how);
|
||||
if (err == -1)
|
||||
return APIError::SHUTDOWN_FAILED;
|
||||
if (how == SHUT_RDWR) {
|
||||
state_ = State::CLOSED;
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
// Give this helper a name for logging
|
||||
virtual void set_log_info(std::string info) = 0;
|
||||
// Get the frame header padding required by this protocol
|
||||
virtual uint8_t frame_header_padding() = 0;
|
||||
// Get the frame footer size required by this protocol
|
||||
virtual uint8_t frame_footer_size() = 0;
|
||||
void set_log_info(std::string info) { info_ = std::move(info); }
|
||||
|
||||
protected:
|
||||
// Common implementation for writing raw data to socket
|
||||
template<typename StateEnum>
|
||||
APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf,
|
||||
const std::string &info, StateEnum &state, StateEnum failed_state);
|
||||
// Struct for holding parsed frame data
|
||||
struct ParsedFrame {
|
||||
std::vector<uint8_t> msg;
|
||||
};
|
||||
|
||||
uint8_t frame_header_padding_{0};
|
||||
uint8_t frame_footer_size_{0};
|
||||
// Buffer containing data to be sent
|
||||
struct SendBuffer {
|
||||
std::vector<uint8_t> data;
|
||||
uint16_t offset{0}; // Current offset within the buffer (uint16_t to reduce memory usage)
|
||||
|
||||
// Using uint16_t reduces memory usage since ESPHome API messages are limited to 64KB max
|
||||
uint16_t remaining() const { return static_cast<uint16_t>(data.size()) - offset; }
|
||||
const uint8_t *current_data() const { return data.data() + offset; }
|
||||
};
|
||||
|
||||
// Queue of data buffers to be sent
|
||||
std::deque<SendBuffer> tx_buf_;
|
||||
|
||||
// Common state enum for all frame helpers
|
||||
// Note: Not all states are used by all implementations
|
||||
// - INITIALIZE: Used by both Noise and Plaintext
|
||||
// - CLIENT_HELLO, SERVER_HELLO, HANDSHAKE: Only used by Noise protocol
|
||||
// - DATA: Used by both Noise and Plaintext
|
||||
// - CLOSED: Used by both Noise and Plaintext
|
||||
// - FAILED: Used by both Noise and Plaintext
|
||||
// - EXPLICIT_REJECT: Only used by Noise protocol
|
||||
enum class State {
|
||||
INITIALIZE = 1,
|
||||
CLIENT_HELLO = 2, // Noise only
|
||||
SERVER_HELLO = 3, // Noise only
|
||||
HANDSHAKE = 4, // Noise only
|
||||
DATA = 5,
|
||||
CLOSED = 6,
|
||||
FAILED = 7,
|
||||
EXPLICIT_REJECT = 8, // Noise only
|
||||
};
|
||||
|
||||
// Current state of the frame helper
|
||||
State state_{State::INITIALIZE};
|
||||
|
||||
// Helper name for logging
|
||||
std::string info_;
|
||||
|
||||
// Socket for communication
|
||||
socket::Socket *socket_{nullptr};
|
||||
std::unique_ptr<socket::Socket> socket_owned_;
|
||||
|
||||
// Common implementation for writing raw data to socket
|
||||
APIError write_raw_(const struct iovec *iov, int iovcnt);
|
||||
|
||||
// Try to send data from the tx buffer
|
||||
APIError try_send_tx_buf_();
|
||||
|
||||
// Helper method to buffer data from IOVs
|
||||
void buffer_data_from_iov_(const struct iovec *iov, int iovcnt, size_t total_write_len);
|
||||
};
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
class APINoiseFrameHelper : public APIFrameHelper {
|
||||
public:
|
||||
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx)
|
||||
: socket_(std::move(socket)), ctx_(std::move(ctx)) {
|
||||
// Noise header structure:
|
||||
// Pos 0: indicator (0x01)
|
||||
// Pos 1-2: encrypted payload size (16-bit big-endian)
|
||||
// Pos 3-6: encrypted type (16-bit) + data_len (16-bit)
|
||||
// Pos 7+: actual payload data
|
||||
frame_header_padding_ = 7;
|
||||
}
|
||||
: APIFrameHelper(std::move(socket)), ctx_(std::move(ctx)) {}
|
||||
~APINoiseFrameHelper() override;
|
||||
APIError init() override;
|
||||
APIError loop() override;
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
bool can_write_without_blocking() override;
|
||||
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
|
||||
std::string getpeername() override { return this->socket_->getpeername(); }
|
||||
int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
|
||||
return this->socket_->getpeername(addr, addrlen);
|
||||
}
|
||||
APIError close() override;
|
||||
APIError shutdown(int how) override;
|
||||
// Give this helper a name for logging
|
||||
void set_log_info(std::string info) override { info_ = std::move(info); }
|
||||
// Get the frame header padding required by this protocol
|
||||
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
||||
// Get the frame footer size required by this protocol
|
||||
uint8_t frame_footer_size() override { return frame_footer_size_; }
|
||||
APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
|
||||
|
||||
protected:
|
||||
struct ParsedFrame {
|
||||
std::vector<uint8_t> msg;
|
||||
};
|
||||
|
||||
APIError state_action_();
|
||||
APIError try_read_frame_(ParsedFrame *frame);
|
||||
APIError try_send_tx_buf_();
|
||||
APIError write_frame_(const uint8_t *data, size_t len);
|
||||
inline APIError write_raw_(const struct iovec *iov, int iovcnt) {
|
||||
return APIFrameHelper::write_raw_(iov, iovcnt, socket_.get(), tx_buf_, info_, state_, State::FAILED);
|
||||
}
|
||||
APIError init_handshake_();
|
||||
APIError check_handshake_finished_();
|
||||
void send_explicit_handshake_reject_(const std::string &reason);
|
||||
|
||||
std::unique_ptr<socket::Socket> socket_;
|
||||
|
||||
std::string info_;
|
||||
// Fixed-size header buffer for noise protocol:
|
||||
// 1 byte for indicator + 2 bytes for message size (16-bit value, not varint)
|
||||
// Note: Maximum message size is 65535, with a limit of 128 bytes during handshake phase
|
||||
@@ -147,7 +196,6 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
||||
std::vector<uint8_t> rx_buf_;
|
||||
size_t rx_buf_len_ = 0;
|
||||
|
||||
std::vector<uint8_t> tx_buf_;
|
||||
std::vector<uint8_t> prologue_;
|
||||
|
||||
std::shared_ptr<APINoiseContext> ctx_;
|
||||
@@ -155,64 +203,21 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
||||
NoiseCipherState *send_cipher_{nullptr};
|
||||
NoiseCipherState *recv_cipher_{nullptr};
|
||||
NoiseProtocolId nid_;
|
||||
|
||||
enum class State {
|
||||
INITIALIZE = 1,
|
||||
CLIENT_HELLO = 2,
|
||||
SERVER_HELLO = 3,
|
||||
HANDSHAKE = 4,
|
||||
DATA = 5,
|
||||
CLOSED = 6,
|
||||
FAILED = 7,
|
||||
EXPLICIT_REJECT = 8,
|
||||
} state_ = State::INITIALIZE;
|
||||
};
|
||||
#endif // USE_API_NOISE
|
||||
|
||||
#ifdef USE_API_PLAINTEXT
|
||||
class APIPlaintextFrameHelper : public APIFrameHelper {
|
||||
public:
|
||||
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(std::move(socket)) {
|
||||
// Plaintext header structure (worst case):
|
||||
// Pos 0: indicator (0x00)
|
||||
// Pos 1-3: payload size varint (up to 3 bytes)
|
||||
// Pos 4-5: message type varint (up to 2 bytes)
|
||||
// Pos 6+: actual payload data
|
||||
frame_header_padding_ = 6;
|
||||
}
|
||||
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : APIFrameHelper(std::move(socket)) {}
|
||||
~APIPlaintextFrameHelper() override = default;
|
||||
APIError init() override;
|
||||
APIError loop() override;
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
bool can_write_without_blocking() override;
|
||||
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
|
||||
std::string getpeername() override { return this->socket_->getpeername(); }
|
||||
int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
|
||||
return this->socket_->getpeername(addr, addrlen);
|
||||
}
|
||||
APIError close() override;
|
||||
APIError shutdown(int how) override;
|
||||
// Give this helper a name for logging
|
||||
void set_log_info(std::string info) override { info_ = std::move(info); }
|
||||
// Get the frame header padding required by this protocol
|
||||
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
||||
// Get the frame footer size required by this protocol
|
||||
uint8_t frame_footer_size() override { return frame_footer_size_; }
|
||||
APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
|
||||
|
||||
protected:
|
||||
struct ParsedFrame {
|
||||
std::vector<uint8_t> msg;
|
||||
};
|
||||
|
||||
APIError try_read_frame_(ParsedFrame *frame);
|
||||
APIError try_send_tx_buf_();
|
||||
inline APIError write_raw_(const struct iovec *iov, int iovcnt) {
|
||||
return APIFrameHelper::write_raw_(iov, iovcnt, socket_.get(), tx_buf_, info_, state_, State::FAILED);
|
||||
}
|
||||
|
||||
std::unique_ptr<socket::Socket> socket_;
|
||||
|
||||
std::string info_;
|
||||
// Fixed-size header buffer for plaintext protocol:
|
||||
// We only need space for the two varints since we validate the indicator byte separately.
|
||||
// To match noise protocol's maximum message size (65535), we need:
|
||||
@@ -229,15 +234,6 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
|
||||
|
||||
std::vector<uint8_t> rx_buf_;
|
||||
size_t rx_buf_len_ = 0;
|
||||
|
||||
std::vector<uint8_t> tx_buf_;
|
||||
|
||||
enum class State {
|
||||
INITIALIZE = 1,
|
||||
DATA = 2,
|
||||
CLOSED = 3,
|
||||
FAILED = 4,
|
||||
} state_ = State::INITIALIZE;
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
@@ -96,6 +96,8 @@ template<> const char *proto_enum_to_string<enums::ColorMode>(enums::ColorMode v
|
||||
return "COLOR_MODE_UNKNOWN";
|
||||
case enums::COLOR_MODE_ON_OFF:
|
||||
return "COLOR_MODE_ON_OFF";
|
||||
case enums::COLOR_MODE_LEGACY_BRIGHTNESS:
|
||||
return "COLOR_MODE_LEGACY_BRIGHTNESS";
|
||||
case enums::COLOR_MODE_BRIGHTNESS:
|
||||
return "COLOR_MODE_BRIGHTNESS";
|
||||
case enums::COLOR_MODE_WHITE:
|
||||
|
||||
@@ -41,7 +41,8 @@ enum FanDirection : uint32_t {
|
||||
enum ColorMode : uint32_t {
|
||||
COLOR_MODE_UNKNOWN = 0,
|
||||
COLOR_MODE_ON_OFF = 1,
|
||||
COLOR_MODE_BRIGHTNESS = 2,
|
||||
COLOR_MODE_LEGACY_BRIGHTNESS = 2,
|
||||
COLOR_MODE_BRIGHTNESS = 3,
|
||||
COLOR_MODE_WHITE = 7,
|
||||
COLOR_MODE_COLOR_TEMPERATURE = 11,
|
||||
COLOR_MODE_COLD_WARM_WHITE = 19,
|
||||
|
||||
@@ -83,34 +83,6 @@ class ProtoVarInt {
|
||||
return static_cast<int64_t>(this->value_ >> 1);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Encode the varint value to a pre-allocated buffer without bounds checking.
|
||||
*
|
||||
* @param buffer The pre-allocated buffer to write the encoded varint to
|
||||
* @param len The size of the buffer in bytes
|
||||
*
|
||||
* @note The caller is responsible for ensuring the buffer is large enough
|
||||
* to hold the encoded value. Use ProtoSize::varint() to calculate
|
||||
* the exact size needed before calling this method.
|
||||
* @note No bounds checking is performed for performance reasons.
|
||||
*/
|
||||
void encode_to_buffer_unchecked(uint8_t *buffer, size_t len) {
|
||||
uint64_t val = this->value_;
|
||||
if (val <= 0x7F) {
|
||||
buffer[0] = val;
|
||||
return;
|
||||
}
|
||||
size_t i = 0;
|
||||
while (val && i < len) {
|
||||
uint8_t temp = val & 0x7F;
|
||||
val >>= 7;
|
||||
if (val) {
|
||||
buffer[i++] = temp | 0x80;
|
||||
} else {
|
||||
buffer[i++] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
void encode(std::vector<uint8_t> &out) {
|
||||
uint64_t val = this->value_;
|
||||
if (val <= 0x7F) {
|
||||
|
||||
@@ -14,8 +14,11 @@ namespace esphome {
|
||||
namespace at581x {
|
||||
|
||||
class AT581XComponent : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
#ifdef USE_SWITCH
|
||||
protected:
|
||||
switch_::Switch *rf_power_switch_{nullptr};
|
||||
|
||||
public:
|
||||
void set_rf_power_switch(switch_::Switch *s) {
|
||||
this->rf_power_switch_ = s;
|
||||
s->turn_on();
|
||||
@@ -45,9 +48,6 @@ class AT581XComponent : public Component, public i2c::I2CDevice {
|
||||
bool i2c_read_reg(uint8_t addr, uint8_t &data);
|
||||
|
||||
protected:
|
||||
#ifdef USE_SWITCH
|
||||
switch_::Switch *rf_power_switch_{nullptr};
|
||||
#endif
|
||||
int freq_;
|
||||
int self_check_time_ms_; /*!< Power-on self-test time, range: 0 ~ 65536 ms */
|
||||
int protect_time_ms_; /*!< Protection time, recommended 1000 ms */
|
||||
|
||||
@@ -7,7 +7,7 @@ CODEOWNERS = ["@bazuchan"]
|
||||
ballu_ns = cg.esphome_ns.namespace("ballu")
|
||||
BalluClimate = ballu_ns.class_("BalluClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(BalluClimate)
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(BalluClimate)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "bedjet_hub.h"
|
||||
#include "bedjet_child.h"
|
||||
#include "bedjet_const.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -15,21 +15,17 @@ void BinarySensor::publish_state(bool state) {
|
||||
if (!this->publish_dedup_.next(state))
|
||||
return;
|
||||
if (this->filter_list_ == nullptr) {
|
||||
this->send_state_internal(state, false);
|
||||
this->send_state_internal(state);
|
||||
} else {
|
||||
this->filter_list_->input(state, false);
|
||||
this->filter_list_->input(state);
|
||||
}
|
||||
}
|
||||
void BinarySensor::publish_initial_state(bool state) {
|
||||
if (!this->publish_dedup_.next(state))
|
||||
return;
|
||||
if (this->filter_list_ == nullptr) {
|
||||
this->send_state_internal(state, true);
|
||||
} else {
|
||||
this->filter_list_->input(state, true);
|
||||
}
|
||||
this->has_state_ = false;
|
||||
this->publish_state(state);
|
||||
}
|
||||
void BinarySensor::send_state_internal(bool state, bool is_initial) {
|
||||
void BinarySensor::send_state_internal(bool state) {
|
||||
bool is_initial = !this->has_state_;
|
||||
if (is_initial) {
|
||||
ESP_LOGD(TAG, "'%s': Sending initial state %s", this->get_name().c_str(), ONOFF(state));
|
||||
} else {
|
||||
|
||||
@@ -67,7 +67,7 @@ class BinarySensor : public EntityBase, public EntityBase_DeviceClass {
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
void send_state_internal(bool state, bool is_initial);
|
||||
void send_state_internal(bool state);
|
||||
|
||||
/// Return whether this binary sensor has outputted a state.
|
||||
virtual bool has_state() const;
|
||||
|
||||
@@ -9,37 +9,37 @@ namespace binary_sensor {
|
||||
|
||||
static const char *const TAG = "sensor.filter";
|
||||
|
||||
void Filter::output(bool value, bool is_initial) {
|
||||
void Filter::output(bool value) {
|
||||
if (!this->dedup_.next(value))
|
||||
return;
|
||||
|
||||
if (this->next_ == nullptr) {
|
||||
this->parent_->send_state_internal(value, is_initial);
|
||||
this->parent_->send_state_internal(value);
|
||||
} else {
|
||||
this->next_->input(value, is_initial);
|
||||
this->next_->input(value);
|
||||
}
|
||||
}
|
||||
void Filter::input(bool value, bool is_initial) {
|
||||
auto b = this->new_value(value, is_initial);
|
||||
void Filter::input(bool value) {
|
||||
auto b = this->new_value(value);
|
||||
if (b.has_value()) {
|
||||
this->output(*b, is_initial);
|
||||
this->output(*b);
|
||||
}
|
||||
}
|
||||
|
||||
optional<bool> DelayedOnOffFilter::new_value(bool value, bool is_initial) {
|
||||
optional<bool> DelayedOnOffFilter::new_value(bool value) {
|
||||
if (value) {
|
||||
this->set_timeout("ON_OFF", this->on_delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
|
||||
this->set_timeout("ON_OFF", this->on_delay_.value(), [this]() { this->output(true); });
|
||||
} else {
|
||||
this->set_timeout("ON_OFF", this->off_delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
|
||||
this->set_timeout("ON_OFF", this->off_delay_.value(), [this]() { this->output(false); });
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
|
||||
optional<bool> DelayedOnFilter::new_value(bool value) {
|
||||
if (value) {
|
||||
this->set_timeout("ON", this->delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
|
||||
this->set_timeout("ON", this->delay_.value(), [this]() { this->output(true); });
|
||||
return {};
|
||||
} else {
|
||||
this->cancel_timeout("ON");
|
||||
@@ -49,9 +49,9 @@ optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
|
||||
|
||||
float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) {
|
||||
optional<bool> DelayedOffFilter::new_value(bool value) {
|
||||
if (!value) {
|
||||
this->set_timeout("OFF", this->delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
|
||||
this->set_timeout("OFF", this->delay_.value(), [this]() { this->output(false); });
|
||||
return {};
|
||||
} else {
|
||||
this->cancel_timeout("OFF");
|
||||
@@ -61,11 +61,11 @@ optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) {
|
||||
|
||||
float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
optional<bool> InvertFilter::new_value(bool value, bool is_initial) { return !value; }
|
||||
optional<bool> InvertFilter::new_value(bool value) { return !value; }
|
||||
|
||||
AutorepeatFilter::AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings) : timings_(std::move(timings)) {}
|
||||
|
||||
optional<bool> AutorepeatFilter::new_value(bool value, bool is_initial) {
|
||||
optional<bool> AutorepeatFilter::new_value(bool value) {
|
||||
if (value) {
|
||||
// Ignore if already running
|
||||
if (this->active_timing_ != 0)
|
||||
@@ -101,7 +101,7 @@ void AutorepeatFilter::next_timing_() {
|
||||
|
||||
void AutorepeatFilter::next_value_(bool val) {
|
||||
const AutorepeatFilterTiming &timing = this->timings_[this->active_timing_ - 2];
|
||||
this->output(val, false); // This is at least the second one so not initial
|
||||
this->output(val);
|
||||
this->set_timeout("ON_OFF", val ? timing.time_on : timing.time_off, [this, val]() { this->next_value_(!val); });
|
||||
}
|
||||
|
||||
@@ -109,18 +109,18 @@ float AutorepeatFilter::get_setup_priority() const { return setup_priority::HARD
|
||||
|
||||
LambdaFilter::LambdaFilter(std::function<optional<bool>(bool)> f) : f_(std::move(f)) {}
|
||||
|
||||
optional<bool> LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); }
|
||||
optional<bool> LambdaFilter::new_value(bool value) { return this->f_(value); }
|
||||
|
||||
optional<bool> SettleFilter::new_value(bool value, bool is_initial) {
|
||||
optional<bool> SettleFilter::new_value(bool value) {
|
||||
if (!this->steady_) {
|
||||
this->set_timeout("SETTLE", this->delay_.value(), [this, value, is_initial]() {
|
||||
this->set_timeout("SETTLE", this->delay_.value(), [this, value]() {
|
||||
this->steady_ = true;
|
||||
this->output(value, is_initial);
|
||||
this->output(value);
|
||||
});
|
||||
return {};
|
||||
} else {
|
||||
this->steady_ = false;
|
||||
this->output(value, is_initial);
|
||||
this->output(value);
|
||||
this->set_timeout("SETTLE", this->delay_.value(), [this]() { this->steady_ = true; });
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -14,11 +14,11 @@ class BinarySensor;
|
||||
|
||||
class Filter {
|
||||
public:
|
||||
virtual optional<bool> new_value(bool value, bool is_initial) = 0;
|
||||
virtual optional<bool> new_value(bool value) = 0;
|
||||
|
||||
void input(bool value, bool is_initial);
|
||||
void input(bool value);
|
||||
|
||||
void output(bool value, bool is_initial);
|
||||
void output(bool value);
|
||||
|
||||
protected:
|
||||
friend BinarySensor;
|
||||
@@ -30,7 +30,7 @@ class Filter {
|
||||
|
||||
class DelayedOnOffFilter : public Filter, public Component {
|
||||
public:
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
@@ -44,7 +44,7 @@ class DelayedOnOffFilter : public Filter, public Component {
|
||||
|
||||
class DelayedOnFilter : public Filter, public Component {
|
||||
public:
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
@@ -56,7 +56,7 @@ class DelayedOnFilter : public Filter, public Component {
|
||||
|
||||
class DelayedOffFilter : public Filter, public Component {
|
||||
public:
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
@@ -68,7 +68,7 @@ class DelayedOffFilter : public Filter, public Component {
|
||||
|
||||
class InvertFilter : public Filter {
|
||||
public:
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
};
|
||||
|
||||
struct AutorepeatFilterTiming {
|
||||
@@ -86,7 +86,7 @@ class AutorepeatFilter : public Filter, public Component {
|
||||
public:
|
||||
explicit AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings);
|
||||
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
@@ -102,7 +102,7 @@ class LambdaFilter : public Filter {
|
||||
public:
|
||||
explicit LambdaFilter(std::function<optional<bool>(bool)> f);
|
||||
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
protected:
|
||||
std::function<optional<bool>(bool)> f_;
|
||||
@@ -110,7 +110,7 @@ class LambdaFilter : public Filter {
|
||||
|
||||
class SettleFilter : public Filter, public Component {
|
||||
public:
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/macros.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
@@ -178,7 +177,7 @@ void BluetoothProxy::loop() {
|
||||
// Flush any pending BLE advertisements that have been accumulated but not yet sent
|
||||
if (this->raw_advertisements_) {
|
||||
static uint32_t last_flush_time = 0;
|
||||
uint32_t now = App.get_loop_component_start_time();
|
||||
uint32_t now = millis();
|
||||
|
||||
// Flush accumulated advertisements every 100ms
|
||||
if (now - last_flush_time >= 100) {
|
||||
|
||||
@@ -40,7 +40,7 @@ def climate_ir_schema(
|
||||
)
|
||||
|
||||
|
||||
def climate_ir_with_receiver_schema(
|
||||
def climare_ir_with_receiver_schema(
|
||||
class_: MockObjClass,
|
||||
) -> cv.Schema:
|
||||
return climate_ir_schema(class_).extend(
|
||||
@@ -59,7 +59,7 @@ def deprecated_schema_constant(config):
|
||||
type = str(id.type).split("::", maxsplit=1)[0]
|
||||
_LOGGER.warning(
|
||||
"Using `climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA` is deprecated and will be removed in ESPHome 2025.11.0. "
|
||||
"Please use `climate_ir.climate_ir_with_receiver_schema(...)` instead. "
|
||||
"Please use `climate_ir.climare_ir_with_receiver_schema(...)` instead. "
|
||||
"If you are seeing this, report an issue to the external_component author and ask them to update it. "
|
||||
"https://developers.esphome.io/blog/2025/05/14/_schema-deprecations/. "
|
||||
"Component using this schema: %s",
|
||||
@@ -68,7 +68,7 @@ def deprecated_schema_constant(config):
|
||||
return config
|
||||
|
||||
|
||||
CLIMATE_IR_WITH_RECEIVER_SCHEMA = climate_ir_with_receiver_schema(ClimateIR)
|
||||
CLIMATE_IR_WITH_RECEIVER_SCHEMA = climare_ir_with_receiver_schema(ClimateIR)
|
||||
CLIMATE_IR_WITH_RECEIVER_SCHEMA.add_extra(deprecated_schema_constant)
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ CONF_BIT_HIGH = "bit_high"
|
||||
CONF_BIT_ONE_LOW = "bit_one_low"
|
||||
CONF_BIT_ZERO_LOW = "bit_zero_low"
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(LgIrClimate).extend(
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(LgIrClimate).extend(
|
||||
{
|
||||
cv.Optional(
|
||||
CONF_HEADER_HIGH, default="8000us"
|
||||
|
||||
@@ -7,7 +7,7 @@ CODEOWNERS = ["@glmnet"]
|
||||
coolix_ns = cg.esphome_ns.namespace("coolix")
|
||||
CoolixClimate = coolix_ns.class_("CoolixClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(CoolixClimate)
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(CoolixClimate)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "cse7766.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cse7766 {
|
||||
@@ -8,7 +7,7 @@ namespace cse7766 {
|
||||
static const char *const TAG = "cse7766";
|
||||
|
||||
void CSE7766Component::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_transmission_ >= 500) {
|
||||
// last transmission too long ago. Reset RX index.
|
||||
this->raw_data_index_ = 0;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "current_based_cover.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include <cfloat>
|
||||
|
||||
namespace esphome {
|
||||
@@ -61,7 +60,7 @@ void CurrentBasedCover::loop() {
|
||||
if (this->current_operation == COVER_OPERATION_IDLE)
|
||||
return;
|
||||
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
|
||||
if (this->current_operation == COVER_OPERATION_OPENING) {
|
||||
if (this->malfunction_detection_ && this->is_closing_()) { // Malfunction
|
||||
|
||||
@@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
|
||||
daikin_ns = cg.esphome_ns.namespace("daikin")
|
||||
DaikinClimate = daikin_ns.class_("DaikinClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DaikinClimate)
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DaikinClimate)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
|
||||
daikin_arc_ns = cg.esphome_ns.namespace("daikin_arc")
|
||||
DaikinArcClimate = daikin_arc_ns.class_("DaikinArcClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DaikinArcClimate)
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DaikinArcClimate)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -9,7 +9,7 @@ daikin_brc_ns = cg.esphome_ns.namespace("daikin_brc")
|
||||
DaikinBrcClimate = daikin_brc_ns.class_("DaikinBrcClimate", climate_ir.ClimateIR)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DaikinBrcClimate).extend(
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DaikinBrcClimate).extend(
|
||||
{
|
||||
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "daly_bms.h"
|
||||
#include <vector>
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace daly_bms {
|
||||
@@ -33,7 +32,7 @@ void DalyBmsComponent::update() {
|
||||
}
|
||||
|
||||
void DalyBmsComponent::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
if (this->receiving_ && (now - this->last_transmission_ >= 200)) {
|
||||
// last transmission too long ago. Reset RX index.
|
||||
ESP_LOGW(TAG, "Last transmission too long ago. Reset RX index.");
|
||||
|
||||
@@ -70,7 +70,7 @@ void DebugComponent::loop() {
|
||||
#ifdef USE_SENSOR
|
||||
// calculate loop time - from last call to this one
|
||||
if (this->loop_time_sensor_ != nullptr) {
|
||||
uint32_t now = App.get_loop_component_start_time();
|
||||
uint32_t now = millis();
|
||||
uint32_t loop_time = now - this->last_loop_timetag_;
|
||||
this->max_loop_time_ = std::max(this->max_loop_time_, loop_time);
|
||||
this->last_loop_timetag_ = now;
|
||||
|
||||
@@ -34,15 +34,13 @@ class DebugComponent : public PollingComponent {
|
||||
#endif
|
||||
void set_loop_time_sensor(sensor::Sensor *loop_time_sensor) { loop_time_sensor_ = loop_time_sensor; }
|
||||
#ifdef USE_ESP32
|
||||
void on_shutdown() override;
|
||||
void set_psram_sensor(sensor::Sensor *psram_sensor) { this->psram_sensor_ = psram_sensor; }
|
||||
#endif // USE_ESP32
|
||||
void set_cpu_frequency_sensor(sensor::Sensor *cpu_frequency_sensor) {
|
||||
this->cpu_frequency_sensor_ = cpu_frequency_sensor;
|
||||
}
|
||||
#endif // USE_SENSOR
|
||||
#ifdef USE_ESP32
|
||||
void on_shutdown() override;
|
||||
#endif // USE_ESP32
|
||||
protected:
|
||||
uint32_t free_heap_{};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
|
||||
delonghi_ns = cg.esphome_ns.namespace("delonghi")
|
||||
DelonghiClimate = delonghi_ns.class_("DelonghiClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DelonghiClimate)
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DelonghiClimate)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -7,7 +7,7 @@ AUTO_LOAD = ["climate_ir"]
|
||||
emmeti_ns = cg.esphome_ns.namespace("emmeti")
|
||||
EmmetiClimate = emmeti_ns.class_("EmmetiClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(EmmetiClimate)
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(EmmetiClimate)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "endstop_cover.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace endstop {
|
||||
@@ -66,7 +65,7 @@ void EndstopCover::loop() {
|
||||
if (this->current_operation == COVER_OPERATION_IDLE)
|
||||
return;
|
||||
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
|
||||
if (this->current_operation == COVER_OPERATION_OPENING && this->is_open_()) {
|
||||
float dur = (now - this->start_dir_time_) / 1e3f;
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
#ifdef USE_ARDUINO
|
||||
#include <Esp.h>
|
||||
#else
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
|
||||
#include <esp_clk_tree.h>
|
||||
#endif
|
||||
|
||||
void setup();
|
||||
void loop();
|
||||
#endif
|
||||
@@ -64,13 +63,7 @@ uint32_t arch_get_cpu_cycle_count() { return cpu_hal_get_cycle_count(); }
|
||||
uint32_t arch_get_cpu_freq_hz() {
|
||||
uint32_t freq = 0;
|
||||
#ifdef USE_ESP_IDF
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
|
||||
esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_CPU, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &freq);
|
||||
#else
|
||||
rtc_cpu_freq_config_t config;
|
||||
rtc_clk_cpu_freq_get_config(&config);
|
||||
freq = config.freq_mhz * 1000000U;
|
||||
#endif
|
||||
#elif defined(USE_ARDUINO)
|
||||
freq = ESP.getCpuFreqMHz() * 1000000;
|
||||
#endif
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <cstring>
|
||||
#include "ble_uuid.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble {
|
||||
@@ -144,7 +143,7 @@ void BLEAdvertising::loop() {
|
||||
if (this->raw_advertisements_callbacks_.empty()) {
|
||||
return;
|
||||
}
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_advertisement_time_ > this->advertising_cycle_time_) {
|
||||
this->stop();
|
||||
this->current_adv_index_ += 1;
|
||||
|
||||
@@ -296,7 +296,7 @@ async def to_code(config):
|
||||
add_idf_component(
|
||||
name="esp32-camera",
|
||||
repo="https://github.com/espressif/esp32-camera.git",
|
||||
ref="v2.0.15",
|
||||
ref="v2.0.9",
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_STREAM_START, []):
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "esp32_camera.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
#include <freertos/task.h>
|
||||
|
||||
@@ -55,7 +54,11 @@ void ESP32Camera::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " HREF Pin: %d", conf.pin_href);
|
||||
ESP_LOGCONFIG(TAG, " Pixel Clock Pin: %d", conf.pin_pclk);
|
||||
ESP_LOGCONFIG(TAG, " External Clock: Pin:%d Frequency:%u", conf.pin_xclk, conf.xclk_freq_hz);
|
||||
#ifdef USE_ESP_IDF // Temporary until the espressif/esp32-camera library is updated
|
||||
ESP_LOGCONFIG(TAG, " I2C Pins: SDA:%d SCL:%d", conf.pin_sscb_sda, conf.pin_sscb_scl);
|
||||
#else
|
||||
ESP_LOGCONFIG(TAG, " I2C Pins: SDA:%d SCL:%d", conf.pin_sccb_sda, conf.pin_sccb_scl);
|
||||
#endif
|
||||
ESP_LOGCONFIG(TAG, " Reset Pin: %d", conf.pin_reset);
|
||||
switch (this->config_.frame_size) {
|
||||
case FRAMESIZE_QQVGA:
|
||||
@@ -159,7 +162,7 @@ void ESP32Camera::loop() {
|
||||
}
|
||||
|
||||
// request idle image every idle_update_interval
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) {
|
||||
this->last_idle_request_ = now;
|
||||
this->request_image(IDLE);
|
||||
@@ -235,8 +238,13 @@ void ESP32Camera::set_external_clock(uint8_t pin, uint32_t frequency) {
|
||||
this->config_.xclk_freq_hz = frequency;
|
||||
}
|
||||
void ESP32Camera::set_i2c_pins(uint8_t sda, uint8_t scl) {
|
||||
#ifdef USE_ESP_IDF // Temporary until the espressif/esp32-camera library is updated
|
||||
this->config_.pin_sscb_sda = sda;
|
||||
this->config_.pin_sscb_scl = scl;
|
||||
#else
|
||||
this->config_.pin_sccb_sda = sda;
|
||||
this->config_.pin_sccb_scl = scl;
|
||||
#endif
|
||||
}
|
||||
void ESP32Camera::set_reset_pin(uint8_t pin) { this->config_.pin_reset = pin; }
|
||||
void ESP32Camera::set_power_down_pin(uint8_t pin) { this->config_.pin_pwdn = pin; }
|
||||
|
||||
@@ -92,7 +92,7 @@ void ESP32ImprovComponent::loop() {
|
||||
|
||||
if (!this->incoming_data_.empty())
|
||||
this->process_incoming_data_();
|
||||
uint32_t now = App.get_loop_component_start_time();
|
||||
uint32_t now = millis();
|
||||
|
||||
switch (this->state_) {
|
||||
case improv::STATE_STOPPED:
|
||||
|
||||
@@ -288,7 +288,7 @@ uint32_t ESP32TouchComponent::component_touch_pad_read(touch_pad_t tp) {
|
||||
}
|
||||
|
||||
void ESP32TouchComponent::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
bool should_print = this->setup_mode_ && now - this->setup_mode_last_log_print_ > 250;
|
||||
for (auto *child : this->children_) {
|
||||
child->value_ = this->component_touch_pad_read(child->get_touch_pad());
|
||||
|
||||
@@ -240,7 +240,7 @@ void EthernetComponent::setup() {
|
||||
}
|
||||
|
||||
void EthernetComponent::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
|
||||
switch (this->state_) {
|
||||
case EthernetComponentState::STOPPED:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "feedback_cover.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace feedback {
|
||||
@@ -221,7 +220,7 @@ void FeedbackCover::set_open_obstacle_sensor(binary_sensor::BinarySensor *open_o
|
||||
void FeedbackCover::loop() {
|
||||
if (this->current_operation == COVER_OPERATION_IDLE)
|
||||
return;
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
|
||||
// Recompute position every loop cycle
|
||||
this->recompute_position_();
|
||||
|
||||
@@ -8,7 +8,7 @@ FujitsuGeneralClimate = fujitsu_general_ns.class_(
|
||||
"FujitsuGeneralClimate", climate_ir.ClimateIR
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(FujitsuGeneralClimate)
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(FujitsuGeneralClimate)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
*/
|
||||
#include "gcja5.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include <cstring>
|
||||
|
||||
namespace esphome {
|
||||
@@ -17,7 +16,7 @@ static const char *const TAG = "gcja5";
|
||||
void GCJA5Component::setup() { ESP_LOGCONFIG(TAG, "Setting up gcja5..."); }
|
||||
|
||||
void GCJA5Component::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_transmission_ >= 500) {
|
||||
// last transmission too long ago. Reset RX index.
|
||||
this->rx_message_.clear();
|
||||
|
||||
@@ -21,7 +21,7 @@ MODELS = {
|
||||
"yag": Model.GREE_YAG,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(GreeClimate).extend(
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(GreeClimate).extend(
|
||||
{
|
||||
cv.Required(CONF_MODEL): cv.enum(MODELS),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "growatt_solar.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace growatt_solar {
|
||||
@@ -19,7 +18,7 @@ void GrowattSolar::loop() {
|
||||
|
||||
void GrowattSolar::update() {
|
||||
// If our last send has had no reply yet, and it wasn't that long ago, do nothing.
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
uint32_t now = millis();
|
||||
if (now - this->last_send_ < this->get_update_interval() / 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ VERTICAL_DIRECTIONS = {
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
climate_ir.climate_ir_with_receiver_schema(HeatpumpIRClimate).extend(
|
||||
climate_ir.climare_ir_with_receiver_schema(HeatpumpIRClimate).extend(
|
||||
{
|
||||
cv.Required(CONF_PROTOCOL): cv.enum(PROTOCOLS),
|
||||
cv.Required(CONF_HORIZONTAL_DEFAULT): cv.enum(HORIZONTAL_DIRECTIONS),
|
||||
|
||||
@@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
|
||||
hitachi_ac344_ns = cg.esphome_ns.namespace("hitachi_ac344")
|
||||
HitachiClimate = hitachi_ac344_ns.class_("HitachiClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(HitachiClimate)
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(HitachiClimate)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
|
||||
hitachi_ac424_ns = cg.esphome_ns.namespace("hitachi_ac424")
|
||||
HitachiClimate = hitachi_ac424_ns.class_("HitachiClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(HitachiClimate)
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(HitachiClimate)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "kuntze.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace kuntze {
|
||||
@@ -61,7 +60,7 @@ void Kuntze::on_modbus_data(const std::vector<uint8_t> &data) {
|
||||
}
|
||||
|
||||
void Kuntze::loop() {
|
||||
uint32_t now = App.get_loop_component_start_time();
|
||||
uint32_t now = millis();
|
||||
// timeout after 15 seconds
|
||||
if (this->waiting_ && (now - this->last_send_ > 15000)) {
|
||||
ESP_LOGW(TAG, "timed out waiting for response");
|
||||
|
||||
@@ -24,7 +24,6 @@ from esphome.const import (
|
||||
CONF_HARDWARE_UART,
|
||||
CONF_ID,
|
||||
CONF_LEVEL,
|
||||
CONF_LOGGER,
|
||||
CONF_LOGS,
|
||||
CONF_ON_MESSAGE,
|
||||
CONF_TAG,
|
||||
@@ -248,7 +247,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
async def to_code(config):
|
||||
baud_rate = config[CONF_BAUD_RATE]
|
||||
level = config[CONF_LEVEL]
|
||||
CORE.data.setdefault(CONF_LOGGER, {})[CONF_LEVEL] = level
|
||||
initial_level = LOG_LEVELS[config.get(CONF_INITIAL_LEVEL, level)]
|
||||
log = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
|
||||
@@ -5,7 +5,7 @@ from esphome.const import CONF_LEVEL, CONF_LOGGER, ENTITY_CATEGORY_CONFIG, ICON_
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_helpers import register_component, register_parented
|
||||
|
||||
from .. import CONF_LOGGER_ID, LOG_LEVELS, Logger, logger_ns
|
||||
from .. import CONF_LOGGER_ID, LOG_LEVEL_SEVERITY, Logger, logger_ns
|
||||
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
|
||||
@@ -21,10 +21,9 @@ CONFIG_SCHEMA = select.select_schema(
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
parent = await cg.get_variable(config[CONF_LOGGER_ID])
|
||||
levels = list(LOG_LEVELS)
|
||||
index = levels.index(CORE.data[CONF_LOGGER][CONF_LEVEL])
|
||||
levels = LOG_LEVEL_SEVERITY
|
||||
index = levels.index(CORE.config[CONF_LOGGER][CONF_LEVEL])
|
||||
levels = levels[: index + 1]
|
||||
var = await select.new_select(config, options=levels)
|
||||
await register_parented(var, parent)
|
||||
await register_parented(var, config[CONF_LOGGER_ID])
|
||||
await register_component(var, config)
|
||||
|
||||
@@ -36,43 +36,29 @@ from .types import (
|
||||
# this will be populated later, in __init__.py to avoid circular imports.
|
||||
WIDGET_TYPES: dict = {}
|
||||
|
||||
TIME_TEXT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_TIME_FORMAT): cv.string,
|
||||
cv.GenerateID(CONF_TIME): cv.templatable(cv.use_id(RealTimeClock)),
|
||||
}
|
||||
)
|
||||
|
||||
PRINTF_TEXT_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_FORMAT): cv.string,
|
||||
cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_),
|
||||
},
|
||||
),
|
||||
validate_printf,
|
||||
)
|
||||
|
||||
|
||||
def _validate_text(value):
|
||||
"""
|
||||
Do some sanity checking of the format to get better error messages
|
||||
than using cv.Any
|
||||
"""
|
||||
if value is None:
|
||||
raise cv.Invalid("No text specified")
|
||||
if isinstance(value, dict):
|
||||
if CONF_TIME_FORMAT in value:
|
||||
return TIME_TEXT_SCHEMA(value)
|
||||
return PRINTF_TEXT_SCHEMA(value)
|
||||
|
||||
return cv.templatable(cv.string)(value)
|
||||
|
||||
|
||||
# A schema for text properties
|
||||
TEXT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_TEXT): _validate_text,
|
||||
cv.Optional(CONF_TEXT): cv.Any(
|
||||
cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_FORMAT): cv.string,
|
||||
cv.Optional(CONF_ARGS, default=list): cv.ensure_list(
|
||||
cv.lambda_
|
||||
),
|
||||
},
|
||||
),
|
||||
validate_printf,
|
||||
),
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_TIME_FORMAT): cv.string,
|
||||
cv.GenerateID(CONF_TIME): cv.templatable(cv.use_id(RealTimeClock)),
|
||||
}
|
||||
),
|
||||
cv.templatable(cv.string),
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "matrix_keypad.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace matrix_keypad {
|
||||
@@ -29,7 +28,7 @@ void MatrixKeypad::setup() {
|
||||
void MatrixKeypad::loop() {
|
||||
static uint32_t active_start = 0;
|
||||
static int active_key = -1;
|
||||
uint32_t now = App.get_loop_component_start_time();
|
||||
uint32_t now = millis();
|
||||
int key = -1;
|
||||
bool error = false;
|
||||
int pos = 0, row, col;
|
||||
|
||||
52
esphome/components/max7219digit/automation.h
Normal file
52
esphome/components/max7219digit/automation.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include "max7219digit.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace max7219digit {
|
||||
|
||||
template<typename... Ts> class DisplayInvertAction : public Action<Ts...>, public Parented<MAX7219Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(bool, state)
|
||||
|
||||
void play(Ts... x) override {
|
||||
bool state = this->state_.value(x...);
|
||||
this->parent_->invert_on_off(state);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class DisplayVisibilityAction : public Action<Ts...>, public Parented<MAX7219Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(bool, state)
|
||||
|
||||
void play(Ts... x) override {
|
||||
bool state = this->state_.value(x...);
|
||||
this->parent_->turn_on_off(state);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class DisplayReverseAction : public Action<Ts...>, public Parented<MAX7219Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(bool, state)
|
||||
|
||||
void play(Ts... x) override {
|
||||
bool state = this->state_.value(x...);
|
||||
this->parent_->set_reverse(state);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class DisplayIntensityAction : public Action<Ts...>, public Parented<MAX7219Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, state)
|
||||
|
||||
void play(Ts... x) override {
|
||||
uint8_t state = this->state_.value(x...);
|
||||
this->parent_->set_intensity(state);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace max7219digit
|
||||
} // namespace esphome
|
||||
@@ -1,7 +1,14 @@
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import display, spi
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_INTENSITY, CONF_LAMBDA, CONF_NUM_CHIPS
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_INTENSITY,
|
||||
CONF_LAMBDA,
|
||||
CONF_NUM_CHIPS,
|
||||
CONF_STATE,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@rspaargaren"]
|
||||
DEPENDENCIES = ["spi"]
|
||||
@@ -17,6 +24,7 @@ CONF_REVERSE_ENABLE = "reverse_enable"
|
||||
CONF_NUM_CHIP_LINES = "num_chip_lines"
|
||||
CONF_CHIP_LINES_STYLE = "chip_lines_style"
|
||||
|
||||
|
||||
integration_ns = cg.esphome_ns.namespace("max7219digit")
|
||||
ChipLinesStyle = integration_ns.enum("ChipLinesStyle")
|
||||
CHIP_LINES_STYLE = {
|
||||
@@ -99,3 +107,87 @@ async def to_code(config):
|
||||
config[CONF_LAMBDA], [(MAX7219ComponentRef, "it")], return_type=cg.void
|
||||
)
|
||||
cg.add(var.set_writer(lambda_))
|
||||
|
||||
|
||||
DisplayInvertAction = max7219_ns.class_("DisplayInvertAction", automation.Action)
|
||||
DisplayVisibilityAction = max7219_ns.class_(
|
||||
"DisplayVisibilityAction", automation.Action
|
||||
)
|
||||
DisplayReverseAction = max7219_ns.class_("DisplayReverseAction", automation.Action)
|
||||
DisplayIntensityAction = max7219_ns.class_("DisplayIntensityAction", automation.Action)
|
||||
|
||||
|
||||
MAX7219_OFF_ACTION_SCHEMA = automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(MAX7219Component),
|
||||
cv.Optional(CONF_STATE, default=False): False,
|
||||
}
|
||||
)
|
||||
|
||||
MAX7219_ON_ACTION_SCHEMA = automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(MAX7219Component),
|
||||
cv.Optional(CONF_STATE, default=True): True,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"max7129digit.invert_off", DisplayInvertAction, MAX7219_OFF_ACTION_SCHEMA
|
||||
)
|
||||
@automation.register_action(
|
||||
"max7129digit.invert_on", DisplayInvertAction, MAX7219_ON_ACTION_SCHEMA
|
||||
)
|
||||
async def max7129digit_invert_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
cg.add(var.set_state(config[CONF_STATE]))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"max7129digit.turn_off", DisplayVisibilityAction, MAX7219_OFF_ACTION_SCHEMA
|
||||
)
|
||||
@automation.register_action(
|
||||
"max7129digit.turn_on", DisplayVisibilityAction, MAX7219_ON_ACTION_SCHEMA
|
||||
)
|
||||
async def max7129digit_visible_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
cg.add(var.set_state(config[CONF_STATE]))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"max7129digit.reverse_off", DisplayReverseAction, MAX7219_OFF_ACTION_SCHEMA
|
||||
)
|
||||
@automation.register_action(
|
||||
"max7129digit.reverse_on", DisplayReverseAction, MAX7219_ON_ACTION_SCHEMA
|
||||
)
|
||||
async def max7129digit_reverse_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
cg.add(var.set_state(config[CONF_STATE]))
|
||||
return var
|
||||
|
||||
|
||||
MAX7219_INTENSITY_SCHEMA = cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(MAX7219Component),
|
||||
cv.Optional(CONF_INTENSITY, default=15): cv.templatable(
|
||||
cv.int_range(min=0, max=15)
|
||||
),
|
||||
},
|
||||
key=CONF_INTENSITY,
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"max7129digit.intensity", DisplayIntensityAction, MAX7219_INTENSITY_SCHEMA
|
||||
)
|
||||
async def max7129digit_intensity_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_INTENSITY], args, cg.uint8)
|
||||
cg.add(var.set_state(template_))
|
||||
return var
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "max7219font.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -64,7 +63,7 @@ void MAX7219Component::dump_config() {
|
||||
}
|
||||
|
||||
void MAX7219Component::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
const uint32_t millis_since_last_scroll = now - this->last_scroll_;
|
||||
const size_t first_line_size = this->max_displaybuffer_[0].size();
|
||||
// check if the buffer has shrunk past the current position since last update
|
||||
|
||||
@@ -147,11 +147,7 @@ bool StreamingModel::perform_streaming_inference(const int8_t features[PREPROCES
|
||||
this->recent_streaming_probabilities_[this->last_n_index_] = output->data.uint8[0]; // probability;
|
||||
this->unprocessed_probability_status_ = true;
|
||||
}
|
||||
if (this->recent_streaming_probabilities_[this->last_n_index_] < this->probability_cutoff_) {
|
||||
// Only increment ignore windows if less than the probability cutoff; this forces the model to "cool-off" from a
|
||||
// previous detection and calling ``reset_probabilities`` so it avoids duplicate detections
|
||||
this->ignore_windows_ = std::min(this->ignore_windows_ + 1, 0);
|
||||
}
|
||||
this->ignore_windows_ = std::min(this->ignore_windows_ + 1, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ midea_ir_ns = cg.esphome_ns.namespace("midea_ir")
|
||||
MideaIR = midea_ir_ns.class_("MideaIR", climate_ir.ClimateIR)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(MideaIR).extend(
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(MideaIR).extend(
|
||||
{
|
||||
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ VERTICAL_DIRECTIONS = {
|
||||
}
|
||||
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(MitsubishiClimate).extend(
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(MitsubishiClimate).extend(
|
||||
{
|
||||
cv.Optional(CONF_SET_FAN_MODE, default="3levels"): cv.enum(SETFANMODE),
|
||||
cv.Optional(CONF_SUPPORTS_DRY, default=False): cv.boolean,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "modbus.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace modbus {
|
||||
@@ -14,7 +13,7 @@ void Modbus::setup() {
|
||||
}
|
||||
}
|
||||
void Modbus::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
|
||||
while (this->available()) {
|
||||
uint8_t byte;
|
||||
|
||||
@@ -345,7 +345,7 @@ void MQTTClientComponent::loop() {
|
||||
this->disconnect_reason_.reset();
|
||||
}
|
||||
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
|
||||
switch (this->state_) {
|
||||
case MQTT_CLIENT_DISABLED:
|
||||
|
||||
@@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
|
||||
noblex_ns = cg.esphome_ns.namespace("noblex")
|
||||
NoblexClimate = noblex_ns.class_("NoblexClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(NoblexClimate)
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(NoblexClimate)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -75,7 +75,7 @@ class PNGFormat(Format):
|
||||
|
||||
def actions(self):
|
||||
cg.add_define("USE_ONLINE_IMAGE_PNG_SUPPORT")
|
||||
cg.add_library("pngle", "1.1.0")
|
||||
cg.add_library("pngle", "1.0.2")
|
||||
|
||||
|
||||
IMAGE_FORMATS = {
|
||||
|
||||
@@ -34,32 +34,12 @@ static void init_callback(pngle_t *pngle, uint32_t w, uint32_t h) {
|
||||
* @param h The height of the rectangle to draw.
|
||||
* @param rgba The color to paint the rectangle in.
|
||||
*/
|
||||
static void draw_callback(pngle_t *pngle, uint32_t x, uint32_t y, uint32_t w, uint32_t h, const uint8_t rgba[4]) {
|
||||
static void draw_callback(pngle_t *pngle, uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint8_t rgba[4]) {
|
||||
PngDecoder *decoder = (PngDecoder *) pngle_get_user_data(pngle);
|
||||
Color color(rgba[0], rgba[1], rgba[2], rgba[3]);
|
||||
decoder->draw(x, y, w, h, color);
|
||||
}
|
||||
|
||||
PngDecoder::PngDecoder(OnlineImage *image) : ImageDecoder(image) {
|
||||
{
|
||||
pngle_t *pngle = this->allocator_.allocate(1, PNGLE_T_SIZE);
|
||||
if (!pngle) {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory for PNGLE engine!");
|
||||
return;
|
||||
}
|
||||
memset(pngle, 0, PNGLE_T_SIZE);
|
||||
pngle_reset(pngle);
|
||||
this->pngle_ = pngle;
|
||||
}
|
||||
}
|
||||
|
||||
PngDecoder::~PngDecoder() {
|
||||
if (this->pngle_) {
|
||||
pngle_reset(this->pngle_);
|
||||
this->allocator_.deallocate(this->pngle_, PNGLE_T_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
int PngDecoder::prepare(size_t download_size) {
|
||||
ImageDecoder::prepare(download_size);
|
||||
if (!this->pngle_) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "image_decoder.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_ONLINE_IMAGE_PNG_SUPPORT
|
||||
#include <pngle.h>
|
||||
|
||||
@@ -19,14 +18,13 @@ class PngDecoder : public ImageDecoder {
|
||||
*
|
||||
* @param display The image to decode the stream into.
|
||||
*/
|
||||
PngDecoder(OnlineImage *image);
|
||||
~PngDecoder() override;
|
||||
PngDecoder(OnlineImage *image) : ImageDecoder(image), pngle_(pngle_new()) {}
|
||||
~PngDecoder() override { pngle_destroy(this->pngle_); }
|
||||
|
||||
int prepare(size_t download_size) override;
|
||||
int HOT decode(uint8_t *buffer, size_t size) override;
|
||||
|
||||
protected:
|
||||
RAMAllocator<pngle_t> allocator_;
|
||||
pngle_t *pngle_;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "pmsx003.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pmsx003 {
|
||||
@@ -43,7 +42,7 @@ void PMSX003Component::dump_config() {
|
||||
}
|
||||
|
||||
void PMSX003Component::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
|
||||
// If we update less often than it takes the device to stabilise, spin the fan down
|
||||
// rather than running it constantly. It does take some time to stabilise, so we
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "pzem004t.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
@@ -17,7 +16,7 @@ void PZEM004T::setup() {
|
||||
}
|
||||
|
||||
void PZEM004T::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_read_ > 500 && this->available() < 7) {
|
||||
while (this->available())
|
||||
this->read();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "rf_bridge.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
|
||||
@@ -129,7 +128,7 @@ void RFBridgeComponent::write_byte_str_(const std::string &codes) {
|
||||
}
|
||||
|
||||
void RFBridgeComponent::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_bridge_byte_ > 50) {
|
||||
this->rx_buffer_.clear();
|
||||
this->last_bridge_byte_ = now;
|
||||
|
||||
26
esphome/components/runtime_stats/__init__.py
Normal file
26
esphome/components/runtime_stats/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Runtime statistics component for ESPHome.
|
||||
"""
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
|
||||
DEPENDENCIES = []
|
||||
|
||||
CONF_ENABLED = "enabled"
|
||||
CONF_LOG_INTERVAL = "log_interval"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ENABLED, default=True): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_LOG_INTERVAL, default=60000
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
"""Generate code for the runtime statistics component."""
|
||||
cg.add(cg.App.set_runtime_stats_enabled(config[CONF_ENABLED]))
|
||||
cg.add(cg.App.set_runtime_stats_log_interval(config[CONF_LOG_INTERVAL]))
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "sds011.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace sds011 {
|
||||
@@ -76,7 +75,7 @@ void SDS011Component::dump_config() {
|
||||
}
|
||||
|
||||
void SDS011Component::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
if ((now - this->last_transmission_ >= 500) && this->data_index_) {
|
||||
// last transmission too long ago. Reset RX index.
|
||||
ESP_LOGV(TAG, "Last transmission too long ago. Reset RX index.");
|
||||
|
||||
@@ -25,10 +25,6 @@ static const uint16_t SEN5X_CMD_TEMPERATURE_COMPENSATION = 0x60B2;
|
||||
static const uint16_t SEN5X_CMD_VOC_ALGORITHM_STATE = 0x6181;
|
||||
static const uint16_t SEN5X_CMD_VOC_ALGORITHM_TUNING = 0x60D0;
|
||||
|
||||
static const int8_t SEN5X_INDEX_SCALE_FACTOR = 10; // used for VOC and NOx index values
|
||||
static const int8_t SEN5X_MIN_INDEX_VALUE = 1 * SEN5X_INDEX_SCALE_FACTOR; // must be adjusted by the scale factor
|
||||
static const int16_t SEN5X_MAX_INDEX_VALUE = 500 * SEN5X_INDEX_SCALE_FACTOR; // must be adjusted by the scale factor
|
||||
|
||||
void SEN5XComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up sen5x...");
|
||||
|
||||
@@ -92,9 +88,8 @@ void SEN5XComponent::setup() {
|
||||
product_name_.push_back(current_char);
|
||||
// second char
|
||||
current_char = *current_int & 0xFF;
|
||||
if (current_char) {
|
||||
if (current_char)
|
||||
product_name_.push_back(current_char);
|
||||
}
|
||||
}
|
||||
current_int++;
|
||||
} while (current_char && --max);
|
||||
@@ -276,10 +271,10 @@ void SEN5XComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " Low RH/T acceleration mode");
|
||||
break;
|
||||
case MEDIUM_ACCELERATION:
|
||||
ESP_LOGCONFIG(TAG, " Medium RH/T acceleration mode");
|
||||
ESP_LOGCONFIG(TAG, " Medium RH/T accelertion mode");
|
||||
break;
|
||||
case HIGH_ACCELERATION:
|
||||
ESP_LOGCONFIG(TAG, " High RH/T acceleration mode");
|
||||
ESP_LOGCONFIG(TAG, " High RH/T accelertion mode");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -342,61 +337,47 @@ void SEN5XComponent::update() {
|
||||
ESP_LOGD(TAG, "read data error (%d)", this->last_error_);
|
||||
return;
|
||||
}
|
||||
float pm_1_0 = measurements[0] / 10.0;
|
||||
if (measurements[0] == 0xFFFF)
|
||||
pm_1_0 = NAN;
|
||||
float pm_2_5 = measurements[1] / 10.0;
|
||||
if (measurements[1] == 0xFFFF)
|
||||
pm_2_5 = NAN;
|
||||
float pm_4_0 = measurements[2] / 10.0;
|
||||
if (measurements[2] == 0xFFFF)
|
||||
pm_4_0 = NAN;
|
||||
float pm_10_0 = measurements[3] / 10.0;
|
||||
if (measurements[3] == 0xFFFF)
|
||||
pm_10_0 = NAN;
|
||||
float humidity = measurements[4] / 100.0;
|
||||
if (measurements[4] == 0xFFFF)
|
||||
humidity = NAN;
|
||||
float temperature = (int16_t) measurements[5] / 200.0;
|
||||
if (measurements[5] == 0xFFFF)
|
||||
temperature = NAN;
|
||||
float voc = measurements[6] / 10.0;
|
||||
if (measurements[6] == 0xFFFF)
|
||||
voc = NAN;
|
||||
float nox = measurements[7] / 10.0;
|
||||
if (measurements[7] == 0xFFFF)
|
||||
nox = NAN;
|
||||
|
||||
ESP_LOGVV(TAG, "pm_1_0 = 0x%.4x", measurements[0]);
|
||||
float pm_1_0 = measurements[0] == UINT16_MAX ? NAN : measurements[0] / 10.0f;
|
||||
|
||||
ESP_LOGVV(TAG, "pm_2_5 = 0x%.4x", measurements[1]);
|
||||
float pm_2_5 = measurements[1] == UINT16_MAX ? NAN : measurements[1] / 10.0f;
|
||||
|
||||
ESP_LOGVV(TAG, "pm_4_0 = 0x%.4x", measurements[2]);
|
||||
float pm_4_0 = measurements[2] == UINT16_MAX ? NAN : measurements[2] / 10.0f;
|
||||
|
||||
ESP_LOGVV(TAG, "pm_10_0 = 0x%.4x", measurements[3]);
|
||||
float pm_10_0 = measurements[3] == UINT16_MAX ? NAN : measurements[3] / 10.0f;
|
||||
|
||||
ESP_LOGVV(TAG, "humidity = 0x%.4x", measurements[4]);
|
||||
float humidity = measurements[4] == INT16_MAX ? NAN : static_cast<int16_t>(measurements[4]) / 100.0f;
|
||||
|
||||
ESP_LOGVV(TAG, "temperature = 0x%.4x", measurements[5]);
|
||||
float temperature = measurements[5] == INT16_MAX ? NAN : static_cast<int16_t>(measurements[5]) / 200.0f;
|
||||
|
||||
ESP_LOGVV(TAG, "voc = 0x%.4x", measurements[6]);
|
||||
int16_t voc_idx = static_cast<int16_t>(measurements[6]);
|
||||
float voc = (voc_idx < SEN5X_MIN_INDEX_VALUE || voc_idx > SEN5X_MAX_INDEX_VALUE)
|
||||
? NAN
|
||||
: static_cast<float>(voc_idx) / 10.0f;
|
||||
|
||||
ESP_LOGVV(TAG, "nox = 0x%.4x", measurements[7]);
|
||||
int16_t nox_idx = static_cast<int16_t>(measurements[7]);
|
||||
float nox = (nox_idx < SEN5X_MIN_INDEX_VALUE || nox_idx > SEN5X_MAX_INDEX_VALUE)
|
||||
? NAN
|
||||
: static_cast<float>(nox_idx) / 10.0f;
|
||||
|
||||
if (this->pm_1_0_sensor_ != nullptr) {
|
||||
if (this->pm_1_0_sensor_ != nullptr)
|
||||
this->pm_1_0_sensor_->publish_state(pm_1_0);
|
||||
}
|
||||
if (this->pm_2_5_sensor_ != nullptr) {
|
||||
if (this->pm_2_5_sensor_ != nullptr)
|
||||
this->pm_2_5_sensor_->publish_state(pm_2_5);
|
||||
}
|
||||
if (this->pm_4_0_sensor_ != nullptr) {
|
||||
if (this->pm_4_0_sensor_ != nullptr)
|
||||
this->pm_4_0_sensor_->publish_state(pm_4_0);
|
||||
}
|
||||
if (this->pm_10_0_sensor_ != nullptr) {
|
||||
if (this->pm_10_0_sensor_ != nullptr)
|
||||
this->pm_10_0_sensor_->publish_state(pm_10_0);
|
||||
}
|
||||
if (this->temperature_sensor_ != nullptr) {
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
}
|
||||
if (this->humidity_sensor_ != nullptr) {
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
this->humidity_sensor_->publish_state(humidity);
|
||||
}
|
||||
if (this->voc_sensor_ != nullptr) {
|
||||
if (this->voc_sensor_ != nullptr)
|
||||
this->voc_sensor_->publish_state(voc);
|
||||
}
|
||||
if (this->nox_sensor_ != nullptr) {
|
||||
if (this->nox_sensor_ != nullptr)
|
||||
this->nox_sensor_->publish_state(nox);
|
||||
}
|
||||
this->status_clear_warning();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "slow_pwm_output.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace slow_pwm {
|
||||
@@ -40,7 +39,7 @@ void SlowPWMOutput::set_output_state_(bool new_state) {
|
||||
}
|
||||
|
||||
void SlowPWMOutput::loop() {
|
||||
uint32_t now = App.get_loop_component_start_time();
|
||||
uint32_t now = millis();
|
||||
float scaled_state = this->state_ * this->period_;
|
||||
|
||||
if (now - this->period_start_time_ >= this->period_) {
|
||||
|
||||
@@ -174,16 +174,6 @@ AudioPipelineState AudioPipeline::process_state() {
|
||||
}
|
||||
}
|
||||
|
||||
if ((event_bits & EventGroupBits::READER_MESSAGE_ERROR)) {
|
||||
xEventGroupClearBits(this->event_group_, EventGroupBits::READER_MESSAGE_ERROR);
|
||||
return AudioPipelineState::ERROR_READING;
|
||||
}
|
||||
|
||||
if ((event_bits & EventGroupBits::DECODER_MESSAGE_ERROR)) {
|
||||
xEventGroupClearBits(this->event_group_, EventGroupBits::DECODER_MESSAGE_ERROR);
|
||||
return AudioPipelineState::ERROR_DECODING;
|
||||
}
|
||||
|
||||
if ((event_bits & EventGroupBits::READER_MESSAGE_FINISHED) &&
|
||||
(!(event_bits & EventGroupBits::READER_MESSAGE_LOADED_MEDIA_TYPE) &&
|
||||
(event_bits & EventGroupBits::DECODER_MESSAGE_FINISHED))) {
|
||||
@@ -213,6 +203,16 @@ AudioPipelineState AudioPipeline::process_state() {
|
||||
return AudioPipelineState::STOPPED;
|
||||
}
|
||||
|
||||
if ((event_bits & EventGroupBits::READER_MESSAGE_ERROR)) {
|
||||
xEventGroupClearBits(this->event_group_, EventGroupBits::READER_MESSAGE_ERROR);
|
||||
return AudioPipelineState::ERROR_READING;
|
||||
}
|
||||
|
||||
if ((event_bits & EventGroupBits::DECODER_MESSAGE_ERROR)) {
|
||||
xEventGroupClearBits(this->event_group_, EventGroupBits::DECODER_MESSAGE_ERROR);
|
||||
return AudioPipelineState::ERROR_DECODING;
|
||||
}
|
||||
|
||||
if (this->pause_state_) {
|
||||
return AudioPipelineState::PAUSED;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ SprinklerSwitch::SprinklerSwitch(switch_::Switch *off_switch, switch_::Switch *o
|
||||
bool SprinklerSwitch::is_latching_valve() { return (this->off_switch_ != nullptr) && (this->on_switch_ != nullptr); }
|
||||
|
||||
void SprinklerSwitch::loop() {
|
||||
if ((this->pinned_millis_) && (App.get_loop_component_start_time() > this->pinned_millis_ + this->pulse_duration_)) {
|
||||
if ((this->pinned_millis_) && (millis() > this->pinned_millis_ + this->pulse_duration_)) {
|
||||
this->pinned_millis_ = 0; // reset tracker
|
||||
if (this->off_switch_->state) {
|
||||
this->off_switch_->turn_off();
|
||||
@@ -148,23 +148,22 @@ SprinklerValveOperator::SprinklerValveOperator(SprinklerValve *valve, Sprinkler
|
||||
: controller_(controller), valve_(valve) {}
|
||||
|
||||
void SprinklerValveOperator::loop() {
|
||||
uint32_t now = App.get_loop_component_start_time();
|
||||
if (now >= this->start_millis_) { // dummy check
|
||||
if (millis() >= this->start_millis_) { // dummy check
|
||||
switch (this->state_) {
|
||||
case STARTING:
|
||||
if (now > (this->start_millis_ + this->start_delay_)) {
|
||||
if (millis() > (this->start_millis_ + this->start_delay_)) {
|
||||
this->run_(); // start_delay_ has been exceeded, so ensure both valves are on and update the state
|
||||
}
|
||||
break;
|
||||
|
||||
case ACTIVE:
|
||||
if (now > (this->start_millis_ + this->start_delay_ + this->run_duration_)) {
|
||||
if (millis() > (this->start_millis_ + this->start_delay_ + this->run_duration_)) {
|
||||
this->stop(); // start_delay_ + run_duration_ has been exceeded, start shutting down
|
||||
}
|
||||
break;
|
||||
|
||||
case STOPPING:
|
||||
if (now > (this->stop_millis_ + this->stop_delay_)) {
|
||||
if (millis() > (this->stop_millis_ + this->stop_delay_)) {
|
||||
this->kill_(); // stop_delay_has been exceeded, ensure all valves are off
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -7,7 +7,7 @@ CODEOWNERS = ["@glmnet"]
|
||||
tcl112_ns = cg.esphome_ns.namespace("tcl112")
|
||||
Tcl112Climate = tcl112_ns.class_("Tcl112Climate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(Tcl112Climate)
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(Tcl112Climate)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "time_based_cover.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace time_based {
|
||||
@@ -27,7 +26,7 @@ void TimeBasedCover::loop() {
|
||||
if (this->current_operation == COVER_OPERATION_IDLE)
|
||||
return;
|
||||
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
|
||||
// Recompute position every loop cycle
|
||||
this->recompute_position_();
|
||||
|
||||
@@ -16,7 +16,7 @@ MODELS = {
|
||||
"RAC-PT1411HWRU-F": Model.MODEL_RAC_PT1411HWRU_F,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(ToshibaClimate).extend(
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(ToshibaClimate).extend(
|
||||
{
|
||||
cv.Optional(CONF_MODEL, default="generic"): cv.enum(MODELS, upper=True),
|
||||
}
|
||||
|
||||
@@ -54,8 +54,8 @@ async def to_code(config):
|
||||
cg.add(var.set_select_mappings(list(options_map.keys())))
|
||||
parent = await cg.get_variable(config[CONF_TUYA_ID])
|
||||
cg.add(var.set_tuya_parent(parent))
|
||||
if (enum_datapoint := config.get(CONF_ENUM_DATAPOINT, None)) is not None:
|
||||
if enum_datapoint := config.get(CONF_ENUM_DATAPOINT, None) is not None:
|
||||
cg.add(var.set_select_id(enum_datapoint, False))
|
||||
if (int_datapoint := config.get(CONF_INT_DATAPOINT, None)) is not None:
|
||||
if int_datapoint := config.get(CONF_INT_DATAPOINT, None) is not None:
|
||||
cg.add(var.set_select_id(int_datapoint, True))
|
||||
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "uart_switch.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace uart {
|
||||
@@ -9,7 +8,7 @@ static const char *const TAG = "uart.switch";
|
||||
|
||||
void UARTSwitch::loop() {
|
||||
if (this->send_every_) {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_transmission_ > this->send_every_) {
|
||||
this->write_command_(this->state);
|
||||
this->last_transmission_ = now;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "uponor_smatrix_climate.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace uponor_smatrix {
|
||||
@@ -14,7 +13,7 @@ void UponorSmatrixClimate::dump_config() {
|
||||
}
|
||||
|
||||
void UponorSmatrixClimate::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
|
||||
// Publish state after all update packets are processed
|
||||
if (this->last_data_ != 0 && (now - this->last_data_ > 100) && this->target_temperature_raw_ != 0) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "uponor_smatrix.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace uponor_smatrix {
|
||||
@@ -36,7 +35,7 @@ void UponorSmatrixComponent::dump_config() {
|
||||
}
|
||||
|
||||
void UponorSmatrixComponent::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
|
||||
// Discard stale data
|
||||
if (!this->rx_buffer_.empty() && (now - this->last_rx_ > 50)) {
|
||||
|
||||
@@ -8,6 +8,58 @@
|
||||
namespace esphome {
|
||||
namespace weikai {
|
||||
|
||||
/*! @mainpage Weikai source code documentation
|
||||
This documentation provides information about the implementation of the family of WeiKai Components in ESPHome.
|
||||
Here is the class diagram related to Weikai family of components:
|
||||
@image html weikai_class.png
|
||||
|
||||
@section WKRingBuffer_ The WKRingBuffer template class
|
||||
The WKRingBuffer template class has it names implies implement a simple ring buffer helper class. This straightforward
|
||||
container implements FIFO functionality, enabling bytes to be pushed into one side and popped from the other in the
|
||||
order of entry. Implementation is classic and therefore not described in any details.
|
||||
|
||||
@section WeikaiRegister_ The WeikaiRegister class
|
||||
The WeikaiRegister helper class creates objects that act as proxies to the device registers.
|
||||
@details This is an abstract virtual class (interface) that provides all the necessary access to registers while hiding
|
||||
the actual implementation. The access to the registers can be made through an I²C bus in for example for wk2168_i2c
|
||||
component or through a SPI bus for example in the case of the wk2168_spi component. Derived classes will actually
|
||||
performs the specific bus operations.
|
||||
|
||||
@section WeikaiRegisterI2C_ WeikaiRegisterI2C
|
||||
The weikai_i2c::WeikaiRegisterI2C class implements the virtual methods of the WeikaiRegister class for an I2C bus.
|
||||
|
||||
@section WeikaiRegisterSPI_ WeikaiRegisterSPI
|
||||
The weikai_spi::WeikaiRegisterSPI class implements the virtual methods of the WeikaiRegister class for an SPI bus.
|
||||
|
||||
@section WeikaiComponent_ The WeikaiComponent class
|
||||
The WeikaiComponent class stores the information global to a WeiKai family component and provides methods to set/access
|
||||
this information. It also serves as a container for WeikaiChannel instances. This is done by maintaining an array of
|
||||
references these WeikaiChannel instances. This class derives from the esphome::Component classes. This class override
|
||||
esphome::Component::loop() method to facilitate the seamless transfer of accumulated bytes from the receive
|
||||
FIFO into the ring buffer. This process ensures quick access to the stored bytes, enhancing the overall efficiency of
|
||||
the component.
|
||||
|
||||
@section WeikaiComponentI2C_ WeikaiComponentI2C
|
||||
The weikai_i2c::WeikaiComponentI2C class implements the virtual methods of the WeikaiComponent class for an I2C bus.
|
||||
|
||||
@section WeikaiComponentSPI_ WeikaiComponentSPI
|
||||
The weikai_spi::WeikaiComponentSPI class implements the virtual methods of the WeikaiComponent class for an SPI bus.
|
||||
|
||||
@section WeikaiGPIOPin_ WeikaiGPIOPin class
|
||||
The WeikaiGPIOPin class is an helper class to expose the GPIO pins of WK family components as if they were internal
|
||||
GPIO pins. It also provides the setup() and dump_summary() methods.
|
||||
|
||||
@section WeikaiChannel_ The WeikaiChannel class
|
||||
The WeikaiChannel class is used to implement all the virtual methods of the ESPHome uart::UARTComponent class. An
|
||||
individual instance of this class is created for each UART channel. It has a link back to the WeikaiComponent object it
|
||||
belongs to. This class derives from the uart::UARTComponent class. It collaborates through an aggregation with
|
||||
WeikaiComponent. This implies that WeikaiComponent acts as a container, housing several WeikaiChannel instances.
|
||||
Furthermore, the WeikaiChannel class derives from the ESPHome uart::UARTComponent class, it also has an association
|
||||
relationship with the WKRingBuffer and WeikaiRegister helper classes. Consequently, when a WeikaiChannel instance is
|
||||
destroyed, the associated WKRingBuffer instance is also destroyed.
|
||||
|
||||
*/
|
||||
|
||||
static const char *const TAG = "weikai";
|
||||
|
||||
/// @brief convert an int to binary representation as C++ std::string
|
||||
|
||||
@@ -15,7 +15,7 @@ MODELS = {
|
||||
"DG11J1-91": Model.MODEL_DG11J1_91,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(WhirlpoolClimate).extend(
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(WhirlpoolClimate).extend(
|
||||
{
|
||||
cv.Optional(CONF_MODEL, default="DG11J1-3A"): cv.enum(MODELS, upper=True),
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ whynter_ns = cg.esphome_ns.namespace("whynter")
|
||||
Whynter = whynter_ns.class_("Whynter", climate_ir.ClimateIR)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(Whynter).extend(
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(Whynter).extend(
|
||||
{
|
||||
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ CODEOWNERS = ["@cfeenstra1024"]
|
||||
zhlt01_ns = cg.esphome_ns.namespace("zhlt01")
|
||||
ZHLT01Climate = zhlt01_ns.class_("ZHLT01Climate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(ZHLT01Climate)
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(ZHLT01Climate)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Constants used by esphome."""
|
||||
|
||||
__version__ = "2025.5.1"
|
||||
__version__ = "2025.6.0-dev"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
|
||||
@@ -35,8 +35,6 @@ void Application::setup() {
|
||||
for (uint32_t i = 0; i < this->components_.size(); i++) {
|
||||
Component *component = this->components_[i];
|
||||
|
||||
// Update loop_component_start_time_ before calling each component during setup
|
||||
this->loop_component_start_time_ = millis();
|
||||
component->call();
|
||||
this->scheduler.process_to_add();
|
||||
this->feed_wdt();
|
||||
@@ -51,8 +49,6 @@ void Application::setup() {
|
||||
this->scheduler.call();
|
||||
this->feed_wdt();
|
||||
for (uint32_t j = 0; j <= i; j++) {
|
||||
// Update loop_component_start_time_ right before calling each component
|
||||
this->loop_component_start_time_ = millis();
|
||||
this->components_[j]->call();
|
||||
new_app_state |= this->components_[j]->get_component_state();
|
||||
this->app_state_ |= new_app_state;
|
||||
@@ -71,32 +67,22 @@ void Application::loop() {
|
||||
uint32_t new_app_state = 0;
|
||||
|
||||
this->scheduler.call();
|
||||
|
||||
// Get the initial loop time at the start
|
||||
uint32_t last_op_end_time = millis();
|
||||
|
||||
// Feed WDT with time
|
||||
this->feed_wdt(last_op_end_time);
|
||||
|
||||
this->feed_wdt();
|
||||
for (Component *component : this->looping_components_) {
|
||||
// Update the cached time before each component runs
|
||||
this->loop_component_start_time_ = last_op_end_time;
|
||||
|
||||
{
|
||||
this->set_current_component(component);
|
||||
WarnIfComponentBlockingGuard guard{component, last_op_end_time};
|
||||
WarnIfComponentBlockingGuard guard{component};
|
||||
component->call();
|
||||
// Use the finish method to get the current time as the end time
|
||||
last_op_end_time = guard.finish();
|
||||
}
|
||||
new_app_state |= component->get_component_state();
|
||||
this->app_state_ |= new_app_state;
|
||||
this->feed_wdt(last_op_end_time);
|
||||
this->feed_wdt();
|
||||
}
|
||||
this->app_state_ = new_app_state;
|
||||
|
||||
// Use the last component's end time instead of calling millis() again
|
||||
auto elapsed = last_op_end_time - this->last_loop_;
|
||||
const uint32_t now = millis();
|
||||
|
||||
auto elapsed = now - this->last_loop_;
|
||||
if (elapsed >= this->loop_interval_ || HighFrequencyLoopRequester::is_high_frequency()) {
|
||||
yield();
|
||||
} else {
|
||||
@@ -108,7 +94,7 @@ void Application::loop() {
|
||||
delay_time = std::min(next_schedule, delay_time);
|
||||
delay(delay_time);
|
||||
}
|
||||
this->last_loop_ = last_op_end_time;
|
||||
this->last_loop_ = now;
|
||||
|
||||
if (this->dump_config_at_ < this->components_.size()) {
|
||||
if (this->dump_config_at_ == 0) {
|
||||
@@ -123,12 +109,10 @@ void Application::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
void IRAM_ATTR HOT Application::feed_wdt(uint32_t time) {
|
||||
void IRAM_ATTR HOT Application::feed_wdt() {
|
||||
static uint32_t last_feed = 0;
|
||||
// Use provided time if available, otherwise get current time
|
||||
uint32_t now = time ? time : millis();
|
||||
// Compare in milliseconds (3ms threshold)
|
||||
if (now - last_feed > 3) {
|
||||
uint32_t now = micros();
|
||||
if (now - last_feed > 3000) {
|
||||
arch_feed_wdt();
|
||||
last_feed = now;
|
||||
#ifdef USE_STATUS_LED
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/runtime_stats.h"
|
||||
#include "esphome/core/scheduler.h"
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
@@ -217,9 +218,6 @@ class Application {
|
||||
|
||||
std::string get_compilation_time() const { return this->compilation_time_; }
|
||||
|
||||
/// Get the cached time in milliseconds from when the current component started its loop execution
|
||||
inline uint32_t IRAM_ATTR HOT get_loop_component_start_time() const { return this->loop_component_start_time_; }
|
||||
|
||||
/** Set the target interval with which to run the loop() calls.
|
||||
* If the loop() method takes longer than the target interval, ESPHome won't
|
||||
* sleep in loop(), but if the time spent in loop() is small than the target, ESPHome
|
||||
@@ -237,9 +235,21 @@ class Application {
|
||||
|
||||
uint32_t get_loop_interval() const { return this->loop_interval_; }
|
||||
|
||||
/** Enable or disable runtime statistics collection.
|
||||
*
|
||||
* @param enable Whether to enable runtime statistics collection.
|
||||
*/
|
||||
void set_runtime_stats_enabled(bool enable) { runtime_stats.set_enabled(enable); }
|
||||
|
||||
/** Set the interval at which runtime statistics are logged.
|
||||
*
|
||||
* @param interval The interval in milliseconds between logging of runtime statistics.
|
||||
*/
|
||||
void set_runtime_stats_log_interval(uint32_t interval) { runtime_stats.set_log_interval(interval); }
|
||||
|
||||
void schedule_dump_config() { this->dump_config_at_ = 0; }
|
||||
|
||||
void feed_wdt(uint32_t time = 0);
|
||||
void feed_wdt();
|
||||
|
||||
void reboot();
|
||||
|
||||
@@ -554,7 +564,6 @@ class Application {
|
||||
size_t dump_config_at_{SIZE_MAX};
|
||||
uint32_t app_state_{0};
|
||||
Component *current_component_{nullptr};
|
||||
uint32_t loop_component_start_time_{0};
|
||||
};
|
||||
|
||||
/// Global storage of Application pointer - only one Application can exist.
|
||||
|
||||
@@ -240,12 +240,16 @@ void PollingComponent::stop_poller() {
|
||||
uint32_t PollingComponent::get_update_interval() const { return this->update_interval_; }
|
||||
void PollingComponent::set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
|
||||
|
||||
WarnIfComponentBlockingGuard::WarnIfComponentBlockingGuard(Component *component, uint32_t start_time)
|
||||
: started_(start_time), component_(component) {}
|
||||
uint32_t WarnIfComponentBlockingGuard::finish() {
|
||||
uint32_t curr_time = millis();
|
||||
WarnIfComponentBlockingGuard::WarnIfComponentBlockingGuard(Component *component)
|
||||
: started_(millis()), component_(component) {}
|
||||
WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {
|
||||
uint32_t current_time = millis();
|
||||
uint32_t blocking_time = current_time - this->started_;
|
||||
|
||||
uint32_t blocking_time = curr_time - this->started_;
|
||||
// Record component runtime stats
|
||||
runtime_stats.record_component_time(this->component_, blocking_time, current_time);
|
||||
|
||||
// Original blocking check logic
|
||||
bool should_warn;
|
||||
if (this->component_ != nullptr) {
|
||||
should_warn = this->component_->should_warn_of_blocking(blocking_time);
|
||||
@@ -257,10 +261,6 @@ uint32_t WarnIfComponentBlockingGuard::finish() {
|
||||
ESP_LOGW(TAG, "Component %s took a long time for an operation (%" PRIu32 " ms).", src, blocking_time);
|
||||
ESP_LOGW(TAG, "Components should block for at most 30 ms.");
|
||||
}
|
||||
|
||||
return curr_time;
|
||||
}
|
||||
|
||||
WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {}
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "esphome/core/optional.h"
|
||||
#include "esphome/core/runtime_stats.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -339,11 +340,7 @@ class PollingComponent : public Component {
|
||||
|
||||
class WarnIfComponentBlockingGuard {
|
||||
public:
|
||||
WarnIfComponentBlockingGuard(Component *component, uint32_t start_time);
|
||||
|
||||
// Finish the timing operation and return the current time
|
||||
uint32_t finish();
|
||||
|
||||
WarnIfComponentBlockingGuard(Component *component);
|
||||
~WarnIfComponentBlockingGuard();
|
||||
|
||||
protected:
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace esphome {
|
||||
|
||||
/*! @mainpage ESPHome source code documentation
|
||||
This documentation provides references to the ESPHome source code classes and methods.
|
||||
|
||||
@details This documentation site is purely for reference and does not contain any user documentation.
|
||||
If you are contributing to ESPHome or building an ESPHome component, then you should be starting at
|
||||
https://developers.esphome.io.
|
||||
*/
|
||||
|
||||
} // namespace esphome
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
|
||||
28
esphome/core/runtime_stats.cpp
Normal file
28
esphome/core/runtime_stats.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include "esphome/core/runtime_stats.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
RuntimeStatsCollector runtime_stats;
|
||||
|
||||
void RuntimeStatsCollector::record_component_time(Component *component, uint32_t duration_ms, uint32_t current_time) {
|
||||
if (!this->enabled_ || component == nullptr)
|
||||
return;
|
||||
|
||||
const char *component_source = component->get_component_source();
|
||||
this->component_stats_[component_source].record_time(duration_ms);
|
||||
|
||||
// If next_log_time_ is 0, initialize it
|
||||
if (this->next_log_time_ == 0) {
|
||||
this->next_log_time_ = current_time + this->log_interval_;
|
||||
return;
|
||||
}
|
||||
|
||||
if (current_time >= this->next_log_time_) {
|
||||
this->log_stats_();
|
||||
this->reset_stats_();
|
||||
this->next_log_time_ = current_time + this->log_interval_;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
161
esphome/core/runtime_stats.h
Normal file
161
esphome/core/runtime_stats.h
Normal file
@@ -0,0 +1,161 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
static const char *const RUNTIME_TAG = "runtime";
|
||||
|
||||
class Component; // Forward declaration
|
||||
|
||||
class ComponentRuntimeStats {
|
||||
public:
|
||||
ComponentRuntimeStats()
|
||||
: period_count_(0),
|
||||
total_count_(0),
|
||||
period_time_ms_(0),
|
||||
total_time_ms_(0),
|
||||
period_max_time_ms_(0),
|
||||
total_max_time_ms_(0) {}
|
||||
|
||||
void record_time(uint32_t duration_ms) {
|
||||
// Update period counters
|
||||
this->period_count_++;
|
||||
this->period_time_ms_ += duration_ms;
|
||||
if (duration_ms > this->period_max_time_ms_)
|
||||
this->period_max_time_ms_ = duration_ms;
|
||||
|
||||
// Update total counters
|
||||
this->total_count_++;
|
||||
this->total_time_ms_ += duration_ms;
|
||||
if (duration_ms > this->total_max_time_ms_)
|
||||
this->total_max_time_ms_ = duration_ms;
|
||||
}
|
||||
|
||||
void reset_period_stats() {
|
||||
this->period_count_ = 0;
|
||||
this->period_time_ms_ = 0;
|
||||
this->period_max_time_ms_ = 0;
|
||||
}
|
||||
|
||||
// Period stats (reset each logging interval)
|
||||
uint32_t get_period_count() const { return this->period_count_; }
|
||||
uint32_t get_period_time_ms() const { return this->period_time_ms_; }
|
||||
uint32_t get_period_max_time_ms() const { return this->period_max_time_ms_; }
|
||||
float get_period_avg_time_ms() const {
|
||||
return this->period_count_ > 0 ? this->period_time_ms_ / static_cast<float>(this->period_count_) : 0.0f;
|
||||
}
|
||||
|
||||
// Total stats (persistent until reboot)
|
||||
uint32_t get_total_count() const { return this->total_count_; }
|
||||
uint32_t get_total_time_ms() const { return this->total_time_ms_; }
|
||||
uint32_t get_total_max_time_ms() const { return this->total_max_time_ms_; }
|
||||
float get_total_avg_time_ms() const {
|
||||
return this->total_count_ > 0 ? this->total_time_ms_ / static_cast<float>(this->total_count_) : 0.0f;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Period stats (reset each logging interval)
|
||||
uint32_t period_count_;
|
||||
uint32_t period_time_ms_;
|
||||
uint32_t period_max_time_ms_;
|
||||
|
||||
// Total stats (persistent until reboot)
|
||||
uint32_t total_count_;
|
||||
uint32_t total_time_ms_;
|
||||
uint32_t total_max_time_ms_;
|
||||
};
|
||||
|
||||
// For sorting components by run time
|
||||
struct ComponentStatPair {
|
||||
std::string name;
|
||||
const ComponentRuntimeStats *stats;
|
||||
|
||||
bool operator>(const ComponentStatPair &other) const {
|
||||
// Sort by period time as that's what we're displaying in the logs
|
||||
return stats->get_period_time_ms() > other.stats->get_period_time_ms();
|
||||
}
|
||||
};
|
||||
|
||||
class RuntimeStatsCollector {
|
||||
public:
|
||||
RuntimeStatsCollector() : log_interval_(60000), next_log_time_(0), enabled_(true) {}
|
||||
|
||||
void set_log_interval(uint32_t log_interval) { this->log_interval_ = log_interval; }
|
||||
uint32_t get_log_interval() const { return this->log_interval_; }
|
||||
|
||||
void set_enabled(bool enabled) { this->enabled_ = enabled; }
|
||||
bool is_enabled() const { return this->enabled_; }
|
||||
|
||||
void record_component_time(Component *component, uint32_t duration_ms, uint32_t current_time);
|
||||
|
||||
protected:
|
||||
void log_stats_() {
|
||||
ESP_LOGI(RUNTIME_TAG, "Component Runtime Statistics");
|
||||
ESP_LOGI(RUNTIME_TAG, "Period stats (last %" PRIu32 "ms):", this->log_interval_);
|
||||
|
||||
// First collect stats we want to display
|
||||
std::vector<ComponentStatPair> stats_to_display;
|
||||
|
||||
for (const auto &it : this->component_stats_) {
|
||||
const ComponentRuntimeStats &stats = it.second;
|
||||
if (stats.get_period_count() > 0) {
|
||||
ComponentStatPair pair = {it.first, &stats};
|
||||
stats_to_display.push_back(pair);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by period runtime (descending)
|
||||
std::sort(stats_to_display.begin(), stats_to_display.end(), std::greater<ComponentStatPair>());
|
||||
|
||||
// Log top components by period runtime
|
||||
for (const auto &it : stats_to_display) {
|
||||
const std::string &source = it.name;
|
||||
const ComponentRuntimeStats *stats = it.stats;
|
||||
|
||||
ESP_LOGI(RUNTIME_TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms",
|
||||
source.c_str(), stats->get_period_count(), stats->get_period_avg_time_ms(),
|
||||
stats->get_period_max_time_ms(), stats->get_period_time_ms());
|
||||
}
|
||||
|
||||
// Log total stats since boot
|
||||
ESP_LOGI(RUNTIME_TAG, "Total stats (since boot):");
|
||||
|
||||
// Re-sort by total runtime for all-time stats
|
||||
std::sort(stats_to_display.begin(), stats_to_display.end(),
|
||||
[](const ComponentStatPair &a, const ComponentStatPair &b) {
|
||||
return a.stats->get_total_time_ms() > b.stats->get_total_time_ms();
|
||||
});
|
||||
|
||||
for (const auto &it : stats_to_display) {
|
||||
const std::string &source = it.name;
|
||||
const ComponentRuntimeStats *stats = it.stats;
|
||||
|
||||
ESP_LOGI(RUNTIME_TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms",
|
||||
source.c_str(), stats->get_total_count(), stats->get_total_avg_time_ms(), stats->get_total_max_time_ms(),
|
||||
stats->get_total_time_ms());
|
||||
}
|
||||
}
|
||||
|
||||
void reset_stats_() {
|
||||
for (auto &it : this->component_stats_) {
|
||||
it.second.reset_period_stats();
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string, ComponentRuntimeStats> component_stats_;
|
||||
uint32_t log_interval_;
|
||||
uint32_t next_log_time_;
|
||||
bool enabled_;
|
||||
};
|
||||
|
||||
// Global instance for runtime stats collection
|
||||
extern RuntimeStatsCollector runtime_stats;
|
||||
|
||||
} // namespace esphome
|
||||
@@ -229,11 +229,8 @@ void HOT Scheduler::call() {
|
||||
// - timeouts/intervals get added, potentially invalidating vector pointers
|
||||
// - timeouts/intervals get cancelled
|
||||
{
|
||||
uint32_t now_ms = millis();
|
||||
WarnIfComponentBlockingGuard guard{item->component, now_ms};
|
||||
WarnIfComponentBlockingGuard guard{item->component};
|
||||
item->callback();
|
||||
// Call finish to ensure blocking time is properly calculated and reported
|
||||
guard.finish();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
|
||||
@@ -601,12 +601,10 @@ class DownloadListRequestHandler(BaseHandler):
|
||||
loop = asyncio.get_running_loop()
|
||||
try:
|
||||
downloads_json = await loop.run_in_executor(None, self._get, configuration)
|
||||
except vol.Invalid as exc:
|
||||
_LOGGER.exception("Error while fetching downloads", exc_info=exc)
|
||||
except vol.Invalid:
|
||||
self.send_error(404)
|
||||
return
|
||||
if downloads_json is None:
|
||||
_LOGGER.error("Configuration %s not found", configuration)
|
||||
self.send_error(404)
|
||||
return
|
||||
self.set_status(200)
|
||||
@@ -620,17 +618,14 @@ class DownloadListRequestHandler(BaseHandler):
|
||||
if storage_json is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
config = yaml_util.load_yaml(settings.rel_path(configuration))
|
||||
config = yaml_util.load_yaml(settings.rel_path(configuration))
|
||||
|
||||
if const.CONF_EXTERNAL_COMPONENTS in config:
|
||||
from esphome.components.external_components import (
|
||||
do_external_components_pass,
|
||||
)
|
||||
if const.CONF_EXTERNAL_COMPONENTS in config:
|
||||
from esphome.components.external_components import (
|
||||
do_external_components_pass,
|
||||
)
|
||||
|
||||
do_external_components_pass(config)
|
||||
except vol.Invalid:
|
||||
_LOGGER.info("Could not parse `external_components`, skipping")
|
||||
do_external_components_pass(config)
|
||||
|
||||
from esphome.components.esp32 import VARIANTS as ESP32_VARIANTS
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ dependencies:
|
||||
version: v1.3.1
|
||||
esp32_camera:
|
||||
git: https://github.com/espressif/esp32-camera.git
|
||||
version: v2.0.15
|
||||
version: v2.0.9
|
||||
mdns:
|
||||
git: https://github.com/espressif/esp-protocols.git
|
||||
version: mdns-v1.8.2
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user