Compare commits
30 Commits
integratio
...
ota_base_e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cccbd7797 | ||
|
|
e920ffbef3 | ||
|
|
31b5a9a127 | ||
|
|
6395567f2a | ||
|
|
c8e7a945ae | ||
|
|
f2b5a8e3b1 | ||
|
|
0a836c7001 | ||
|
|
69819cdcc5 | ||
|
|
1756017181 | ||
|
|
11b5b00cd6 | ||
|
|
2c349cad8b | ||
|
|
08f563167f | ||
|
|
8713945c6a | ||
|
|
e7e6b7f89b | ||
|
|
4ec8cd9611 | ||
|
|
b89c5751b1 | ||
|
|
9d1e68e45d | ||
|
|
bc0a248149 | ||
|
|
ec99692963 | ||
|
|
77f7816114 | ||
|
|
f3e7a6887b | ||
|
|
401523c854 | ||
|
|
5b0c249272 | ||
|
|
3f650b2c15 | ||
|
|
a1e3b67683 | ||
|
|
6e45b9d2dd | ||
|
|
c008e6aa1c | ||
|
|
5f764fc019 | ||
|
|
fc5ab71772 | ||
|
|
9632f0248e |
@@ -327,7 +327,6 @@ esphome/components/opentherm/* @olegtarasov
|
||||
esphome/components/openthread/* @mrene
|
||||
esphome/components/opt3001/* @ccutrer
|
||||
esphome/components/ota/* @esphome/core
|
||||
esphome/components/ota_base/* @esphome/core
|
||||
esphome/components/output/* @esphome/core
|
||||
esphome/components/packet_transport/* @clydebarrow
|
||||
esphome/components/pca6416a/* @Mat931
|
||||
@@ -499,6 +498,7 @@ esphome/components/voice_assistant/* @jesserockz @kahrendt
|
||||
esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
|
||||
esphome/components/watchdog/* @oarcher
|
||||
esphome/components/waveshare_epaper/* @clydebarrow
|
||||
esphome/components/web_server/ota/* @esphome/core
|
||||
esphome/components/web_server_base/* @OttoWinter
|
||||
esphome/components/web_server_idf/* @dentra
|
||||
esphome/components/weikai/* @DrCoolZic
|
||||
|
||||
@@ -517,7 +517,7 @@ class APIConnection : public APIServerConnection {
|
||||
|
||||
private:
|
||||
// Helper to cleanup items from the beginning
|
||||
void cleanup_items_(size_t count) {
|
||||
void cleanup_items(size_t count) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
items[i].creator.cleanup(items[i].message_type);
|
||||
}
|
||||
@@ -541,14 +541,14 @@ class APIConnection : public APIServerConnection {
|
||||
|
||||
// Clear all items with proper cleanup
|
||||
void clear() {
|
||||
cleanup_items_(items.size());
|
||||
cleanup_items(items.size());
|
||||
items.clear();
|
||||
batch_start_time = 0;
|
||||
}
|
||||
|
||||
// Remove processed items from the front with proper cleanup
|
||||
void remove_front(size_t count) {
|
||||
cleanup_items_(count);
|
||||
cleanup_items(count);
|
||||
items.erase(items.begin(), items.begin() + count);
|
||||
}
|
||||
|
||||
|
||||
@@ -3493,7 +3493,7 @@ bool SubscribeLogsResponse::decode_length(uint32_t field_id, ProtoLengthDelimite
|
||||
}
|
||||
void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_enum<enums::LogLevel>(1, this->level);
|
||||
buffer.encode_bytes(3, reinterpret_cast<const uint8_t *>(this->message.data()), this->message.size());
|
||||
buffer.encode_string(3, this->message);
|
||||
buffer.encode_bool(4, this->send_failed);
|
||||
}
|
||||
void SubscribeLogsResponse::calculate_size(uint32_t &total_size) const {
|
||||
@@ -3529,9 +3529,7 @@ bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthD
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void NoiseEncryptionSetKeyRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bytes(1, reinterpret_cast<const uint8_t *>(this->key.data()), this->key.size());
|
||||
}
|
||||
void NoiseEncryptionSetKeyRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->key); }
|
||||
void NoiseEncryptionSetKeyRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->key, false);
|
||||
}
|
||||
@@ -4268,7 +4266,7 @@ bool CameraImageResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
}
|
||||
void CameraImageResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_bytes(2, reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size());
|
||||
buffer.encode_string(2, this->data);
|
||||
buffer.encode_bool(3, this->done);
|
||||
}
|
||||
void CameraImageResponse::calculate_size(uint32_t &total_size) const {
|
||||
@@ -6786,7 +6784,7 @@ void BluetoothServiceData::encode(ProtoWriteBuffer buffer) const {
|
||||
for (auto &it : this->legacy_data) {
|
||||
buffer.encode_uint32(2, it, true);
|
||||
}
|
||||
buffer.encode_bytes(3, reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size());
|
||||
buffer.encode_string(3, this->data);
|
||||
}
|
||||
void BluetoothServiceData::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->uuid, false);
|
||||
@@ -6860,7 +6858,7 @@ bool BluetoothLEAdvertisementResponse::decode_length(uint32_t field_id, ProtoLen
|
||||
}
|
||||
void BluetoothLEAdvertisementResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_bytes(2, reinterpret_cast<const uint8_t *>(this->name.data()), this->name.size());
|
||||
buffer.encode_string(2, this->name);
|
||||
buffer.encode_sint32(3, this->rssi);
|
||||
for (auto &it : this->service_uuids) {
|
||||
buffer.encode_string(4, it, true);
|
||||
@@ -6961,7 +6959,7 @@ void BluetoothLERawAdvertisement::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_sint32(2, this->rssi);
|
||||
buffer.encode_uint32(3, this->address_type);
|
||||
buffer.encode_bytes(4, reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size());
|
||||
buffer.encode_string(4, this->data);
|
||||
}
|
||||
void BluetoothLERawAdvertisement::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_uint64_field(total_size, 1, this->address, false);
|
||||
@@ -7494,7 +7492,7 @@ bool BluetoothGATTReadResponse::decode_length(uint32_t field_id, ProtoLengthDeli
|
||||
void BluetoothGATTReadResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_bytes(3, reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size());
|
||||
buffer.encode_string(3, this->data);
|
||||
}
|
||||
void BluetoothGATTReadResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_uint64_field(total_size, 1, this->address, false);
|
||||
@@ -7553,7 +7551,7 @@ void BluetoothGATTWriteRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_bool(3, this->response);
|
||||
buffer.encode_bytes(4, reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size());
|
||||
buffer.encode_string(4, this->data);
|
||||
}
|
||||
void BluetoothGATTWriteRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_uint64_field(total_size, 1, this->address, false);
|
||||
@@ -7650,7 +7648,7 @@ bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, Proto
|
||||
void BluetoothGATTWriteDescriptorRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_bytes(3, reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size());
|
||||
buffer.encode_string(3, this->data);
|
||||
}
|
||||
void BluetoothGATTWriteDescriptorRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_uint64_field(total_size, 1, this->address, false);
|
||||
@@ -7752,7 +7750,7 @@ bool BluetoothGATTNotifyDataResponse::decode_length(uint32_t field_id, ProtoLeng
|
||||
void BluetoothGATTNotifyDataResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_bytes(3, reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size());
|
||||
buffer.encode_string(3, this->data);
|
||||
}
|
||||
void BluetoothGATTNotifyDataResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_uint64_field(total_size, 1, this->address, false);
|
||||
@@ -8482,7 +8480,7 @@ bool VoiceAssistantAudio::decode_length(uint32_t field_id, ProtoLengthDelimited
|
||||
}
|
||||
}
|
||||
void VoiceAssistantAudio::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bytes(1, reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size());
|
||||
buffer.encode_string(1, this->data);
|
||||
buffer.encode_bool(2, this->end);
|
||||
}
|
||||
void VoiceAssistantAudio::calculate_size(uint32_t &total_size) const {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#include <cinttypes>
|
||||
|
||||
#ifdef USE_OTA
|
||||
#include "esphome/components/ota_base/ota_backend.h"
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
|
||||
@@ -61,9 +61,9 @@ void ESP32BLETracker::setup() {
|
||||
global_esp32_ble_tracker = this;
|
||||
|
||||
#ifdef USE_OTA
|
||||
ota_base::get_global_ota_callback()->add_on_state_callback(
|
||||
[this](ota_base::OTAState state, float progress, uint8_t error, ota_base::OTAComponent *comp) {
|
||||
if (state == ota_base::OTA_STARTED) {
|
||||
ota::get_global_ota_callback()->add_on_state_callback(
|
||||
[this](ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) {
|
||||
if (state == ota::OTA_STARTED) {
|
||||
this->stop_scan();
|
||||
for (auto *client : this->clients_) {
|
||||
client->disconnect();
|
||||
|
||||
@@ -21,43 +21,6 @@ static const uint32_t RMT_CLK_FREQ = 80000000;
|
||||
static const uint8_t RMT_CLK_DIV = 2;
|
||||
#endif
|
||||
|
||||
static const size_t RMT_SYMBOLS_PER_BYTE = 8;
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
|
||||
static size_t IRAM_ATTR HOT encoder_callback(const void *data, size_t size, size_t symbols_written, size_t symbols_free,
|
||||
rmt_symbol_word_t *symbols, bool *done, void *arg) {
|
||||
auto *params = static_cast<LedParams *>(arg);
|
||||
const auto *bytes = static_cast<const uint8_t *>(data);
|
||||
size_t index = symbols_written / RMT_SYMBOLS_PER_BYTE;
|
||||
|
||||
// convert byte to symbols
|
||||
if (index < size) {
|
||||
if (symbols_free < RMT_SYMBOLS_PER_BYTE) {
|
||||
return 0;
|
||||
}
|
||||
for (int32_t i = 0; i < RMT_SYMBOLS_PER_BYTE; i++) {
|
||||
if (bytes[index] & (1 << (7 - i))) {
|
||||
symbols[i] = params->bit1;
|
||||
} else {
|
||||
symbols[i] = params->bit0;
|
||||
}
|
||||
}
|
||||
if ((index + 1) >= size && params->reset.duration0 == 0 && params->reset.duration1 == 0) {
|
||||
*done = true;
|
||||
}
|
||||
return RMT_SYMBOLS_PER_BYTE;
|
||||
}
|
||||
|
||||
// send reset
|
||||
if (symbols_free < 1) {
|
||||
return 0;
|
||||
}
|
||||
symbols[0] = params->reset;
|
||||
*done = true;
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
void ESP32RMTLEDStripLightOutput::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Running setup");
|
||||
|
||||
@@ -79,15 +42,10 @@ void ESP32RMTLEDStripLightOutput::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
|
||||
// copy of the led buffer
|
||||
this->rmt_buf_ = allocator.allocate(buffer_size);
|
||||
#else
|
||||
RAMAllocator<rmt_symbol_word_t> rmt_allocator(this->use_psram_ ? 0 : RAMAllocator<rmt_symbol_word_t>::ALLOC_INTERNAL);
|
||||
|
||||
// 8 bits per byte, 1 rmt_symbol_word_t per bit + 1 rmt_symbol_word_t for reset
|
||||
this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 + 1);
|
||||
#endif
|
||||
|
||||
rmt_tx_channel_config_t channel;
|
||||
memset(&channel, 0, sizeof(channel));
|
||||
@@ -107,18 +65,6 @@ void ESP32RMTLEDStripLightOutput::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
|
||||
rmt_simple_encoder_config_t encoder;
|
||||
memset(&encoder, 0, sizeof(encoder));
|
||||
encoder.callback = encoder_callback;
|
||||
encoder.arg = &this->params_;
|
||||
encoder.min_chunk_size = 8;
|
||||
if (rmt_new_simple_encoder(&encoder, &this->encoder_) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Encoder creation failed");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
#else
|
||||
rmt_copy_encoder_config_t encoder;
|
||||
memset(&encoder, 0, sizeof(encoder));
|
||||
if (rmt_new_copy_encoder(&encoder, &this->encoder_) != ESP_OK) {
|
||||
@@ -126,7 +72,6 @@ void ESP32RMTLEDStripLightOutput::setup() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (rmt_enable(this->channel_) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Enabling channel failed");
|
||||
@@ -140,20 +85,20 @@ void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bi
|
||||
float ratio = (float) RMT_CLK_FREQ / RMT_CLK_DIV / 1e09f;
|
||||
|
||||
// 0-bit
|
||||
this->params_.bit0.duration0 = (uint32_t) (ratio * bit0_high);
|
||||
this->params_.bit0.level0 = 1;
|
||||
this->params_.bit0.duration1 = (uint32_t) (ratio * bit0_low);
|
||||
this->params_.bit0.level1 = 0;
|
||||
this->bit0_.duration0 = (uint32_t) (ratio * bit0_high);
|
||||
this->bit0_.level0 = 1;
|
||||
this->bit0_.duration1 = (uint32_t) (ratio * bit0_low);
|
||||
this->bit0_.level1 = 0;
|
||||
// 1-bit
|
||||
this->params_.bit1.duration0 = (uint32_t) (ratio * bit1_high);
|
||||
this->params_.bit1.level0 = 1;
|
||||
this->params_.bit1.duration1 = (uint32_t) (ratio * bit1_low);
|
||||
this->params_.bit1.level1 = 0;
|
||||
this->bit1_.duration0 = (uint32_t) (ratio * bit1_high);
|
||||
this->bit1_.level0 = 1;
|
||||
this->bit1_.duration1 = (uint32_t) (ratio * bit1_low);
|
||||
this->bit1_.level1 = 0;
|
||||
// reset
|
||||
this->params_.reset.duration0 = (uint32_t) (ratio * reset_time_high);
|
||||
this->params_.reset.level0 = 1;
|
||||
this->params_.reset.duration1 = (uint32_t) (ratio * reset_time_low);
|
||||
this->params_.reset.level1 = 0;
|
||||
this->reset_.duration0 = (uint32_t) (ratio * reset_time_high);
|
||||
this->reset_.level0 = 1;
|
||||
this->reset_.duration1 = (uint32_t) (ratio * reset_time_low);
|
||||
this->reset_.level1 = 0;
|
||||
}
|
||||
|
||||
void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
|
||||
@@ -177,9 +122,6 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
|
||||
}
|
||||
delayMicroseconds(50);
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
|
||||
memcpy(this->rmt_buf_, this->buf_, this->get_buffer_size_());
|
||||
#else
|
||||
size_t buffer_size = this->get_buffer_size_();
|
||||
|
||||
size_t size = 0;
|
||||
@@ -189,7 +131,7 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
|
||||
while (size < buffer_size) {
|
||||
uint8_t b = *psrc;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
pdest->val = b & (1 << (7 - i)) ? this->params_.bit1.val : this->params_.bit0.val;
|
||||
pdest->val = b & (1 << (7 - i)) ? this->bit1_.val : this->bit0_.val;
|
||||
pdest++;
|
||||
len++;
|
||||
}
|
||||
@@ -197,20 +139,17 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
|
||||
psrc++;
|
||||
}
|
||||
|
||||
if (this->params_.reset.duration0 > 0 || this->params_.reset.duration1 > 0) {
|
||||
pdest->val = this->params_.reset.val;
|
||||
if (this->reset_.duration0 > 0 || this->reset_.duration1 > 0) {
|
||||
pdest->val = this->reset_.val;
|
||||
pdest++;
|
||||
len++;
|
||||
}
|
||||
#endif
|
||||
|
||||
rmt_transmit_config_t config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
|
||||
error = rmt_transmit(this->channel_, this->encoder_, this->rmt_buf_, this->get_buffer_size_(), &config);
|
||||
#else
|
||||
config.loop_count = 0;
|
||||
config.flags.eot_level = 0;
|
||||
error = rmt_transmit(this->channel_, this->encoder_, this->rmt_buf_, len * sizeof(rmt_symbol_word_t), &config);
|
||||
#endif
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGE(TAG, "RMT TX error");
|
||||
this->status_set_warning();
|
||||
|
||||
@@ -25,12 +25,6 @@ enum RGBOrder : uint8_t {
|
||||
ORDER_BRG,
|
||||
};
|
||||
|
||||
struct LedParams {
|
||||
rmt_symbol_word_t bit0;
|
||||
rmt_symbol_word_t bit1;
|
||||
rmt_symbol_word_t reset;
|
||||
};
|
||||
|
||||
class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
|
||||
public:
|
||||
void setup() override;
|
||||
@@ -78,15 +72,12 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
|
||||
|
||||
uint8_t *buf_{nullptr};
|
||||
uint8_t *effect_data_{nullptr};
|
||||
LedParams params_;
|
||||
rmt_channel_handle_t channel_{nullptr};
|
||||
rmt_encoder_handle_t encoder_{nullptr};
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
|
||||
uint8_t *rmt_buf_{nullptr};
|
||||
#else
|
||||
rmt_symbol_word_t *rmt_buf_{nullptr};
|
||||
#endif
|
||||
rmt_symbol_word_t bit0_, bit1_, reset_;
|
||||
uint32_t rmt_symbols_{48};
|
||||
|
||||
uint8_t pin_;
|
||||
uint16_t num_leds_;
|
||||
bool is_rgbw_{false};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import logging
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code
|
||||
from esphome.components.ota_base import OTAComponent
|
||||
from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code
|
||||
from esphome.config_helpers import merge_config
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
#ifdef USE_OTA
|
||||
#include "esphome/components/md5/md5.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/components/ota_base/ota_backend.h" // For OTAComponent and callbacks
|
||||
#include "esphome/components/ota_base/ota_backend_arduino_esp32.h"
|
||||
#include "esphome/components/ota_base/ota_backend_arduino_esp8266.h"
|
||||
#include "esphome/components/ota_base/ota_backend_arduino_libretiny.h"
|
||||
#include "esphome/components/ota_base/ota_backend_arduino_rp2040.h"
|
||||
#include "esphome/components/ota_base/ota_backend_esp_idf.h"
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_esp32.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_esp8266.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_libretiny.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_rp2040.h"
|
||||
#include "esphome/components/ota/ota_backend_esp_idf.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -23,7 +23,7 @@ static constexpr u_int16_t OTA_BLOCK_SIZE = 8192;
|
||||
|
||||
void ESPHomeOTAComponent::setup() {
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
ota_base::register_ota_platform(this);
|
||||
ota::register_ota_platform(this);
|
||||
#endif
|
||||
|
||||
this->server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
|
||||
@@ -94,7 +94,7 @@ void ESPHomeOTAComponent::loop() {
|
||||
static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01;
|
||||
|
||||
void ESPHomeOTAComponent::handle_() {
|
||||
ota_base::OTAResponseTypes error_code = ota_base::OTA_RESPONSE_ERROR_UNKNOWN;
|
||||
ota::OTAResponseTypes error_code = ota::OTA_RESPONSE_ERROR_UNKNOWN;
|
||||
bool update_started = false;
|
||||
size_t total = 0;
|
||||
uint32_t last_progress = 0;
|
||||
@@ -102,7 +102,7 @@ void ESPHomeOTAComponent::handle_() {
|
||||
char *sbuf = reinterpret_cast<char *>(buf);
|
||||
size_t ota_size;
|
||||
uint8_t ota_features;
|
||||
std::unique_ptr<ota_base::OTABackend> backend;
|
||||
std::unique_ptr<ota::OTABackend> backend;
|
||||
(void) ota_features;
|
||||
#if USE_OTA_VERSION == 2
|
||||
size_t size_acknowledged = 0;
|
||||
@@ -129,7 +129,7 @@ void ESPHomeOTAComponent::handle_() {
|
||||
ESP_LOGD(TAG, "Starting update from %s", this->client_->getpeername().c_str());
|
||||
this->status_set_warning();
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->state_callback_.call(ota_base::OTA_STARTED, 0.0f, 0);
|
||||
this->state_callback_.call(ota::OTA_STARTED, 0.0f, 0);
|
||||
#endif
|
||||
|
||||
if (!this->readall_(buf, 5)) {
|
||||
@@ -140,16 +140,16 @@ void ESPHomeOTAComponent::handle_() {
|
||||
if (buf[0] != 0x6C || buf[1] != 0x26 || buf[2] != 0xF7 || buf[3] != 0x5C || buf[4] != 0x45) {
|
||||
ESP_LOGW(TAG, "Magic bytes do not match! 0x%02X-0x%02X-0x%02X-0x%02X-0x%02X", buf[0], buf[1], buf[2], buf[3],
|
||||
buf[4]);
|
||||
error_code = ota_base::OTA_RESPONSE_ERROR_MAGIC;
|
||||
error_code = ota::OTA_RESPONSE_ERROR_MAGIC;
|
||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||
}
|
||||
|
||||
// Send OK and version - 2 bytes
|
||||
buf[0] = ota_base::OTA_RESPONSE_OK;
|
||||
buf[0] = ota::OTA_RESPONSE_OK;
|
||||
buf[1] = USE_OTA_VERSION;
|
||||
this->writeall_(buf, 2);
|
||||
|
||||
backend = ota_base::make_ota_backend();
|
||||
backend = ota::make_ota_backend();
|
||||
|
||||
// Read features - 1 byte
|
||||
if (!this->readall_(buf, 1)) {
|
||||
@@ -160,16 +160,16 @@ void ESPHomeOTAComponent::handle_() {
|
||||
ESP_LOGV(TAG, "Features: 0x%02X", ota_features);
|
||||
|
||||
// Acknowledge header - 1 byte
|
||||
buf[0] = ota_base::OTA_RESPONSE_HEADER_OK;
|
||||
buf[0] = ota::OTA_RESPONSE_HEADER_OK;
|
||||
if ((ota_features & FEATURE_SUPPORTS_COMPRESSION) != 0 && backend->supports_compression()) {
|
||||
buf[0] = ota_base::OTA_RESPONSE_SUPPORTS_COMPRESSION;
|
||||
buf[0] = ota::OTA_RESPONSE_SUPPORTS_COMPRESSION;
|
||||
}
|
||||
|
||||
this->writeall_(buf, 1);
|
||||
|
||||
#ifdef USE_OTA_PASSWORD
|
||||
if (!this->password_.empty()) {
|
||||
buf[0] = ota_base::OTA_RESPONSE_REQUEST_AUTH;
|
||||
buf[0] = ota::OTA_RESPONSE_REQUEST_AUTH;
|
||||
this->writeall_(buf, 1);
|
||||
md5::MD5Digest md5{};
|
||||
md5.init();
|
||||
@@ -220,14 +220,14 @@ void ESPHomeOTAComponent::handle_() {
|
||||
|
||||
if (!matches) {
|
||||
ESP_LOGW(TAG, "Auth failed! Passwords do not match");
|
||||
error_code = ota_base::OTA_RESPONSE_ERROR_AUTH_INVALID;
|
||||
error_code = ota::OTA_RESPONSE_ERROR_AUTH_INVALID;
|
||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||
}
|
||||
}
|
||||
#endif // USE_OTA_PASSWORD
|
||||
|
||||
// Acknowledge auth OK - 1 byte
|
||||
buf[0] = ota_base::OTA_RESPONSE_AUTH_OK;
|
||||
buf[0] = ota::OTA_RESPONSE_AUTH_OK;
|
||||
this->writeall_(buf, 1);
|
||||
|
||||
// Read size, 4 bytes MSB first
|
||||
@@ -243,12 +243,12 @@ void ESPHomeOTAComponent::handle_() {
|
||||
ESP_LOGV(TAG, "Size is %u bytes", ota_size);
|
||||
|
||||
error_code = backend->begin(ota_size);
|
||||
if (error_code != ota_base::OTA_RESPONSE_OK)
|
||||
if (error_code != ota::OTA_RESPONSE_OK)
|
||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||
update_started = true;
|
||||
|
||||
// Acknowledge prepare OK - 1 byte
|
||||
buf[0] = ota_base::OTA_RESPONSE_UPDATE_PREPARE_OK;
|
||||
buf[0] = ota::OTA_RESPONSE_UPDATE_PREPARE_OK;
|
||||
this->writeall_(buf, 1);
|
||||
|
||||
// Read binary MD5, 32 bytes
|
||||
@@ -261,7 +261,7 @@ void ESPHomeOTAComponent::handle_() {
|
||||
backend->set_update_md5(sbuf);
|
||||
|
||||
// Acknowledge MD5 OK - 1 byte
|
||||
buf[0] = ota_base::OTA_RESPONSE_BIN_MD5_OK;
|
||||
buf[0] = ota::OTA_RESPONSE_BIN_MD5_OK;
|
||||
this->writeall_(buf, 1);
|
||||
|
||||
while (total < ota_size) {
|
||||
@@ -285,14 +285,14 @@ void ESPHomeOTAComponent::handle_() {
|
||||
}
|
||||
|
||||
error_code = backend->write(buf, read);
|
||||
if (error_code != ota_base::OTA_RESPONSE_OK) {
|
||||
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||
ESP_LOGW(TAG, "Error writing binary data to flash!, error_code: %d", error_code);
|
||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||
}
|
||||
total += read;
|
||||
#if USE_OTA_VERSION == 2
|
||||
while (size_acknowledged + OTA_BLOCK_SIZE <= total || (total == ota_size && size_acknowledged < ota_size)) {
|
||||
buf[0] = ota_base::OTA_RESPONSE_CHUNK_OK;
|
||||
buf[0] = ota::OTA_RESPONSE_CHUNK_OK;
|
||||
this->writeall_(buf, 1);
|
||||
size_acknowledged += OTA_BLOCK_SIZE;
|
||||
}
|
||||
@@ -304,7 +304,7 @@ void ESPHomeOTAComponent::handle_() {
|
||||
float percentage = (total * 100.0f) / ota_size;
|
||||
ESP_LOGD(TAG, "Progress: %0.1f%%", percentage);
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->state_callback_.call(ota_base::OTA_IN_PROGRESS, percentage, 0);
|
||||
this->state_callback_.call(ota::OTA_IN_PROGRESS, percentage, 0);
|
||||
#endif
|
||||
// feed watchdog and give other tasks a chance to run
|
||||
App.feed_wdt();
|
||||
@@ -313,21 +313,21 @@ void ESPHomeOTAComponent::handle_() {
|
||||
}
|
||||
|
||||
// Acknowledge receive OK - 1 byte
|
||||
buf[0] = ota_base::OTA_RESPONSE_RECEIVE_OK;
|
||||
buf[0] = ota::OTA_RESPONSE_RECEIVE_OK;
|
||||
this->writeall_(buf, 1);
|
||||
|
||||
error_code = backend->end();
|
||||
if (error_code != ota_base::OTA_RESPONSE_OK) {
|
||||
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||
ESP_LOGW(TAG, "Error ending update! error_code: %d", error_code);
|
||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||
}
|
||||
|
||||
// Acknowledge Update end OK - 1 byte
|
||||
buf[0] = ota_base::OTA_RESPONSE_UPDATE_END_OK;
|
||||
buf[0] = ota::OTA_RESPONSE_UPDATE_END_OK;
|
||||
this->writeall_(buf, 1);
|
||||
|
||||
// Read ACK
|
||||
if (!this->readall_(buf, 1) || buf[0] != ota_base::OTA_RESPONSE_OK) {
|
||||
if (!this->readall_(buf, 1) || buf[0] != ota::OTA_RESPONSE_OK) {
|
||||
ESP_LOGW(TAG, "Reading back acknowledgement failed");
|
||||
// do not go to error, this is not fatal
|
||||
}
|
||||
@@ -338,7 +338,7 @@ void ESPHomeOTAComponent::handle_() {
|
||||
ESP_LOGI(TAG, "Update complete");
|
||||
this->status_clear_warning();
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->state_callback_.call(ota_base::OTA_COMPLETED, 100.0f, 0);
|
||||
this->state_callback_.call(ota::OTA_COMPLETED, 100.0f, 0);
|
||||
#endif
|
||||
delay(100); // NOLINT
|
||||
App.safe_reboot();
|
||||
@@ -355,7 +355,7 @@ error:
|
||||
|
||||
this->status_momentary_error("onerror", 5000);
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->state_callback_.call(ota_base::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
|
||||
this->state_callback_.call(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
#ifdef USE_OTA
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/components/ota_base/ota_backend.h"
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#include "esphome/components/socket/socket.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
/// ESPHomeOTAComponent provides a simple way to integrate Over-the-Air updates into your app using ArduinoOTA.
|
||||
class ESPHomeOTAComponent : public ota_base::OTAComponent {
|
||||
class ESPHomeOTAComponent : public ota::OTAComponent {
|
||||
public:
|
||||
#ifdef USE_OTA_PASSWORD
|
||||
void set_auth_password(const std::string &password) { password_ = password; }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code
|
||||
from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_PASSWORD, CONF_URL, CONF_USERNAME
|
||||
from esphome.core import coroutine_with_priority
|
||||
@@ -15,9 +15,6 @@ DEPENDENCIES = ["network", "http_request"]
|
||||
CONF_MD5 = "md5"
|
||||
CONF_MD5_URL = "md5_url"
|
||||
|
||||
ota_base_ns = cg.esphome_ns.namespace("ota_base")
|
||||
OTAComponent = ota_base_ns.class_("OTAComponent", cg.Component)
|
||||
|
||||
OtaHttpRequestComponent = http_request_ns.class_(
|
||||
"OtaHttpRequestComponent", OTAComponent
|
||||
)
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
|
||||
#include "esphome/components/md5/md5.h"
|
||||
#include "esphome/components/watchdog/watchdog.h"
|
||||
#include "esphome/components/ota_base/ota_backend.h"
|
||||
#include "esphome/components/ota_base/ota_backend_arduino_esp32.h"
|
||||
#include "esphome/components/ota_base/ota_backend_arduino_esp8266.h"
|
||||
#include "esphome/components/ota_base/ota_backend_arduino_rp2040.h"
|
||||
#include "esphome/components/ota_base/ota_backend_esp_idf.h"
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_esp32.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_esp8266.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_rp2040.h"
|
||||
#include "esphome/components/ota/ota_backend_esp_idf.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
@@ -19,7 +19,7 @@ static const char *const TAG = "http_request.ota";
|
||||
|
||||
void OtaHttpRequestComponent::setup() {
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
ota_base::register_ota_platform(this);
|
||||
ota::register_ota_platform(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -50,15 +50,15 @@ void OtaHttpRequestComponent::flash() {
|
||||
|
||||
ESP_LOGI(TAG, "Starting update");
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->state_callback_.call(ota_base::OTA_STARTED, 0.0f, 0);
|
||||
this->state_callback_.call(ota::OTA_STARTED, 0.0f, 0);
|
||||
#endif
|
||||
|
||||
auto ota_status = this->do_ota_();
|
||||
|
||||
switch (ota_status) {
|
||||
case ota_base::OTA_RESPONSE_OK:
|
||||
case ota::OTA_RESPONSE_OK:
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->state_callback_.call(ota_base::OTA_COMPLETED, 100.0f, ota_status);
|
||||
this->state_callback_.call(ota::OTA_COMPLETED, 100.0f, ota_status);
|
||||
#endif
|
||||
delay(10);
|
||||
App.safe_reboot();
|
||||
@@ -66,7 +66,7 @@ void OtaHttpRequestComponent::flash() {
|
||||
|
||||
default:
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->state_callback_.call(ota_base::OTA_ERROR, 0.0f, ota_status);
|
||||
this->state_callback_.call(ota::OTA_ERROR, 0.0f, ota_status);
|
||||
#endif
|
||||
this->md5_computed_.clear(); // will be reset at next attempt
|
||||
this->md5_expected_.clear(); // will be reset at next attempt
|
||||
@@ -74,7 +74,7 @@ void OtaHttpRequestComponent::flash() {
|
||||
}
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponent::cleanup_(std::unique_ptr<ota_base::OTABackend> backend,
|
||||
void OtaHttpRequestComponent::cleanup_(std::unique_ptr<ota::OTABackend> backend,
|
||||
const std::shared_ptr<HttpContainer> &container) {
|
||||
if (this->update_started_) {
|
||||
ESP_LOGV(TAG, "Aborting OTA backend");
|
||||
@@ -115,9 +115,9 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
|
||||
ESP_LOGV(TAG, "MD5Digest initialized");
|
||||
|
||||
ESP_LOGV(TAG, "OTA backend begin");
|
||||
auto backend = ota_base::make_ota_backend();
|
||||
auto backend = ota::make_ota_backend();
|
||||
auto error_code = backend->begin(container->content_length);
|
||||
if (error_code != ota_base::OTA_RESPONSE_OK) {
|
||||
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||
ESP_LOGW(TAG, "backend->begin error: %d", error_code);
|
||||
this->cleanup_(std::move(backend), container);
|
||||
return error_code;
|
||||
@@ -144,7 +144,7 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
|
||||
// write bytes to OTA backend
|
||||
this->update_started_ = true;
|
||||
error_code = backend->write(buf, bufsize);
|
||||
if (error_code != ota_base::OTA_RESPONSE_OK) {
|
||||
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||
// error code explanation available at
|
||||
// https://github.com/esphome/esphome/blob/dev/esphome/components/ota/ota_backend.h
|
||||
ESP_LOGE(TAG, "Error code (%02X) writing binary data to flash at offset %d and size %d", error_code,
|
||||
@@ -160,7 +160,7 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
|
||||
float percentage = container->get_bytes_read() * 100.0f / container->content_length;
|
||||
ESP_LOGD(TAG, "Progress: %0.1f%%", percentage);
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->state_callback_.call(ota_base::OTA_IN_PROGRESS, percentage, 0);
|
||||
this->state_callback_.call(ota::OTA_IN_PROGRESS, percentage, 0);
|
||||
#endif
|
||||
}
|
||||
} // while
|
||||
@@ -174,7 +174,7 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
|
||||
if (strncmp(this->md5_computed_.c_str(), this->md5_expected_.c_str(), MD5_SIZE) != 0) {
|
||||
ESP_LOGE(TAG, "MD5 computed: %s - Aborting due to MD5 mismatch", this->md5_computed_.c_str());
|
||||
this->cleanup_(std::move(backend), container);
|
||||
return ota_base::OTA_RESPONSE_ERROR_MD5_MISMATCH;
|
||||
return ota::OTA_RESPONSE_ERROR_MD5_MISMATCH;
|
||||
} else {
|
||||
backend->set_update_md5(md5_receive_str.get());
|
||||
}
|
||||
@@ -187,14 +187,14 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
|
||||
delay(100); // NOLINT
|
||||
|
||||
error_code = backend->end();
|
||||
if (error_code != ota_base::OTA_RESPONSE_OK) {
|
||||
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||
ESP_LOGW(TAG, "Error ending update! error_code: %d", error_code);
|
||||
this->cleanup_(std::move(backend), container);
|
||||
return error_code;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Update complete");
|
||||
return ota_base::OTA_RESPONSE_OK;
|
||||
return ota::OTA_RESPONSE_OK;
|
||||
}
|
||||
|
||||
std::string OtaHttpRequestComponent::get_url_with_auth_(const std::string &url) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/ota_base/ota_backend.h"
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
@@ -22,7 +22,7 @@ enum OtaHttpRequestError : uint8_t {
|
||||
OTA_CONNECTION_ERROR = 0x12,
|
||||
};
|
||||
|
||||
class OtaHttpRequestComponent : public ota_base::OTAComponent, public Parented<HttpRequestComponent> {
|
||||
class OtaHttpRequestComponent : public ota::OTAComponent, public Parented<HttpRequestComponent> {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
@@ -40,7 +40,7 @@ class OtaHttpRequestComponent : public ota_base::OTAComponent, public Parented<H
|
||||
void flash();
|
||||
|
||||
protected:
|
||||
void cleanup_(std::unique_ptr<ota_base::OTABackend> backend, const std::shared_ptr<HttpContainer> &container);
|
||||
void cleanup_(std::unique_ptr<ota::OTABackend> backend, const std::shared_ptr<HttpContainer> &container);
|
||||
uint8_t do_ota_();
|
||||
std::string get_url_with_auth_(const std::string &url);
|
||||
bool http_get_md5_();
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include "esphome/components/json/json_util.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/components/ota_base/ota_backend.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
@@ -22,13 +21,13 @@ static const char *const TAG = "http_request.update";
|
||||
static const size_t MAX_READ_SIZE = 256;
|
||||
|
||||
void HttpRequestUpdate::setup() {
|
||||
this->ota_parent_->add_on_state_callback([this](ota_base::OTAState state, float progress, uint8_t err) {
|
||||
if (state == ota_base::OTAState::OTA_IN_PROGRESS) {
|
||||
this->ota_parent_->add_on_state_callback([this](ota::OTAState state, float progress, uint8_t err) {
|
||||
if (state == ota::OTAState::OTA_IN_PROGRESS) {
|
||||
this->state_ = update::UPDATE_STATE_INSTALLING;
|
||||
this->update_info_.has_progress = true;
|
||||
this->update_info_.progress = progress;
|
||||
this->publish_state();
|
||||
} else if (state == ota_base::OTAState::OTA_ABORT || state == ota_base::OTAState::OTA_ERROR) {
|
||||
} else if (state == ota::OTAState::OTA_ABORT || state == ota::OTAState::OTA_ERROR) {
|
||||
this->state_ = update::UPDATE_STATE_AVAILABLE;
|
||||
this->status_set_error("Failed to install firmware");
|
||||
this->publish_state();
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "esphome/components/audio/audio_transfer_buffer.h"
|
||||
|
||||
#ifdef USE_OTA
|
||||
#include "esphome/components/ota_base/ota_backend.h"
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
@@ -121,11 +121,11 @@ void MicroWakeWord::setup() {
|
||||
});
|
||||
|
||||
#ifdef USE_OTA
|
||||
ota_base::get_global_ota_callback()->add_on_state_callback(
|
||||
[this](ota_base::OTAState state, float progress, uint8_t error, ota_base::OTAComponent *comp) {
|
||||
if (state == ota_base::OTA_STARTED) {
|
||||
ota::get_global_ota_callback()->add_on_state_callback(
|
||||
[this](ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) {
|
||||
if (state == ota::OTA_STARTED) {
|
||||
this->suspend_task_();
|
||||
} else if (state == ota_base::OTA_ERROR) {
|
||||
} else if (state == ota::OTA_ERROR) {
|
||||
this->resume_task_();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,12 +8,10 @@ from esphome.const import (
|
||||
CONF_PLATFORM,
|
||||
CONF_TRIGGER_ID,
|
||||
)
|
||||
from esphome.core import coroutine_with_priority
|
||||
|
||||
from ..ota_base import OTAState
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
AUTO_LOAD = ["safe_mode", "ota_base"]
|
||||
AUTO_LOAD = ["md5", "safe_mode"]
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
@@ -25,6 +23,8 @@ CONF_ON_STATE_CHANGE = "on_state_change"
|
||||
|
||||
|
||||
ota_ns = cg.esphome_ns.namespace("ota")
|
||||
OTAComponent = ota_ns.class_("OTAComponent", cg.Component)
|
||||
OTAState = ota_ns.enum("OTAState")
|
||||
OTAAbortTrigger = ota_ns.class_("OTAAbortTrigger", automation.Trigger.template())
|
||||
OTAEndTrigger = ota_ns.class_("OTAEndTrigger", automation.Trigger.template())
|
||||
OTAErrorTrigger = ota_ns.class_("OTAErrorTrigger", automation.Trigger.template())
|
||||
@@ -84,6 +84,12 @@ BASE_OTA_SCHEMA = cv.Schema(
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_OTA")
|
||||
|
||||
if CORE.is_esp32 and CORE.using_arduino:
|
||||
cg.add_library("Update", None)
|
||||
|
||||
if CORE.is_rp2040 and CORE.using_arduino:
|
||||
cg.add_library("Updater", None)
|
||||
|
||||
|
||||
async def ota_to_code(var, config):
|
||||
await cg.past_safe_mode()
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
#pragma once
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
#include "esphome/components/ota_base/ota_backend.h"
|
||||
#include "ota_backend.h"
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ota {
|
||||
|
||||
// Import types from ota_base for the automation triggers
|
||||
using ota_base::OTAComponent;
|
||||
using ota_base::OTAState;
|
||||
|
||||
class OTAStateChangeTrigger : public Trigger<OTAState> {
|
||||
public:
|
||||
explicit OTAStateChangeTrigger(OTAComponent *parent) {
|
||||
@@ -26,7 +22,7 @@ class OTAStartTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit OTAStartTrigger(OTAComponent *parent) {
|
||||
parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) {
|
||||
if (state == ota_base::OTA_STARTED && !parent->is_failed()) {
|
||||
if (state == OTA_STARTED && !parent->is_failed()) {
|
||||
trigger();
|
||||
}
|
||||
});
|
||||
@@ -37,7 +33,7 @@ class OTAProgressTrigger : public Trigger<float> {
|
||||
public:
|
||||
explicit OTAProgressTrigger(OTAComponent *parent) {
|
||||
parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) {
|
||||
if (state == ota_base::OTA_IN_PROGRESS && !parent->is_failed()) {
|
||||
if (state == OTA_IN_PROGRESS && !parent->is_failed()) {
|
||||
trigger(progress);
|
||||
}
|
||||
});
|
||||
@@ -48,7 +44,7 @@ class OTAEndTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit OTAEndTrigger(OTAComponent *parent) {
|
||||
parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) {
|
||||
if (state == ota_base::OTA_COMPLETED && !parent->is_failed()) {
|
||||
if (state == OTA_COMPLETED && !parent->is_failed()) {
|
||||
trigger();
|
||||
}
|
||||
});
|
||||
@@ -59,7 +55,7 @@ class OTAAbortTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit OTAAbortTrigger(OTAComponent *parent) {
|
||||
parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) {
|
||||
if (state == ota_base::OTA_ABORT && !parent->is_failed()) {
|
||||
if (state == OTA_ABORT && !parent->is_failed()) {
|
||||
trigger();
|
||||
}
|
||||
});
|
||||
@@ -70,7 +66,7 @@ class OTAErrorTrigger : public Trigger<uint8_t> {
|
||||
public:
|
||||
explicit OTAErrorTrigger(OTAComponent *parent) {
|
||||
parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) {
|
||||
if (state == ota_base::OTA_ERROR && !parent->is_failed()) {
|
||||
if (state == OTA_ERROR && !parent->is_failed()) {
|
||||
trigger(error);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
#include "ota_backend.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ota_base {
|
||||
|
||||
// The make_ota_backend() implementation is provided by each platform-specific backend
|
||||
namespace ota {
|
||||
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
OTAGlobalCallback *global_ota_callback{nullptr}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
@@ -18,5 +16,5 @@ OTAGlobalCallback *get_global_ota_callback() {
|
||||
void register_ota_platform(OTAComponent *ota_caller) { get_global_ota_callback()->register_ota(ota_caller); }
|
||||
#endif
|
||||
|
||||
} // namespace ota_base
|
||||
} // namespace ota
|
||||
} // namespace esphome
|
||||
@@ -9,7 +9,7 @@
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace ota_base {
|
||||
namespace ota {
|
||||
|
||||
enum OTAResponseTypes {
|
||||
OTA_RESPONSE_OK = 0x00,
|
||||
@@ -59,12 +59,10 @@ class OTABackend {
|
||||
virtual bool supports_compression() = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<OTABackend> make_ota_backend();
|
||||
|
||||
class OTAComponent : public Component {
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
public:
|
||||
void add_on_state_callback(std::function<void(OTAState, float, uint8_t)> &&callback) {
|
||||
void add_on_state_callback(std::function<void(ota::OTAState, float, uint8_t)> &&callback) {
|
||||
this->state_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
@@ -82,7 +80,7 @@ class OTAComponent : public Component {
|
||||
* This should be used by OTA implementations that run in separate tasks
|
||||
* (like web_server OTA) to ensure callbacks execute in the main loop.
|
||||
*/
|
||||
void call_deferred(OTAState state, float progress, uint8_t error) {
|
||||
void call_deferred(ota::OTAState state, float progress, uint8_t error) {
|
||||
component_->defer([this, state, progress, error]() { this->call(state, progress, error); });
|
||||
}
|
||||
|
||||
@@ -118,6 +116,7 @@ void register_ota_platform(OTAComponent *ota_caller);
|
||||
// - state_callback_.call_deferred() when in separate task (e.g., web_server OTA)
|
||||
// This ensures proper callback execution in all contexts.
|
||||
#endif
|
||||
std::unique_ptr<ota::OTABackend> make_ota_backend();
|
||||
|
||||
} // namespace ota_base
|
||||
} // namespace ota
|
||||
} // namespace esphome
|
||||
@@ -8,11 +8,11 @@
|
||||
#include <Update.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace ota_base {
|
||||
namespace ota {
|
||||
|
||||
static const char *const TAG = "ota.arduino_esp32";
|
||||
|
||||
std::unique_ptr<OTABackend> make_ota_backend() { return make_unique<ArduinoESP32OTABackend>(); }
|
||||
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoESP32OTABackend>(); }
|
||||
|
||||
OTAResponseTypes ArduinoESP32OTABackend::begin(size_t image_size) {
|
||||
// Handle UPDATE_SIZE_UNKNOWN (0) which is used by web server OTA
|
||||
@@ -66,7 +66,7 @@ OTAResponseTypes ArduinoESP32OTABackend::end() {
|
||||
|
||||
void ArduinoESP32OTABackend::abort() { Update.abort(); }
|
||||
|
||||
} // namespace ota_base
|
||||
} // namespace ota
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32_FRAMEWORK_ARDUINO
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ota_base {
|
||||
namespace ota {
|
||||
|
||||
class ArduinoESP32OTABackend : public OTABackend {
|
||||
public:
|
||||
@@ -21,7 +21,7 @@ class ArduinoESP32OTABackend : public OTABackend {
|
||||
bool md5_set_{false};
|
||||
};
|
||||
|
||||
} // namespace ota_base
|
||||
} // namespace ota
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32_FRAMEWORK_ARDUINO
|
||||
@@ -10,11 +10,11 @@
|
||||
#include <Updater.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace ota_base {
|
||||
namespace ota {
|
||||
|
||||
static const char *const TAG = "ota.arduino_esp8266";
|
||||
|
||||
std::unique_ptr<OTABackend> make_ota_backend() { return make_unique<ArduinoESP8266OTABackend>(); }
|
||||
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoESP8266OTABackend>(); }
|
||||
|
||||
OTAResponseTypes ArduinoESP8266OTABackend::begin(size_t image_size) {
|
||||
// Handle UPDATE_SIZE_UNKNOWN (0) by calculating available space
|
||||
@@ -82,7 +82,7 @@ void ArduinoESP8266OTABackend::abort() {
|
||||
esp8266::preferences_prevent_write(false);
|
||||
}
|
||||
|
||||
} // namespace ota_base
|
||||
} // namespace ota
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "esphome/core/macros.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ota_base {
|
||||
namespace ota {
|
||||
|
||||
class ArduinoESP8266OTABackend : public OTABackend {
|
||||
public:
|
||||
@@ -26,7 +26,7 @@ class ArduinoESP8266OTABackend : public OTABackend {
|
||||
bool md5_set_{false};
|
||||
};
|
||||
|
||||
} // namespace ota_base
|
||||
} // namespace ota
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
@@ -8,11 +8,11 @@
|
||||
#include <Update.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace ota_base {
|
||||
namespace ota {
|
||||
|
||||
static const char *const TAG = "ota.arduino_libretiny";
|
||||
|
||||
std::unique_ptr<OTABackend> make_ota_backend() { return make_unique<ArduinoLibreTinyOTABackend>(); }
|
||||
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoLibreTinyOTABackend>(); }
|
||||
|
||||
OTAResponseTypes ArduinoLibreTinyOTABackend::begin(size_t image_size) {
|
||||
// Handle UPDATE_SIZE_UNKNOWN (0) which is used by web server OTA
|
||||
@@ -66,7 +66,7 @@ OTAResponseTypes ArduinoLibreTinyOTABackend::end() {
|
||||
|
||||
void ArduinoLibreTinyOTABackend::abort() { Update.abort(); }
|
||||
|
||||
} // namespace ota_base
|
||||
} // namespace ota
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_LIBRETINY
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ota_base {
|
||||
namespace ota {
|
||||
|
||||
class ArduinoLibreTinyOTABackend : public OTABackend {
|
||||
public:
|
||||
@@ -20,7 +20,7 @@ class ArduinoLibreTinyOTABackend : public OTABackend {
|
||||
bool md5_set_{false};
|
||||
};
|
||||
|
||||
} // namespace ota_base
|
||||
} // namespace ota
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_LIBRETINY
|
||||
@@ -10,24 +10,15 @@
|
||||
#include <Updater.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace ota_base {
|
||||
namespace ota {
|
||||
|
||||
static const char *const TAG = "ota.arduino_rp2040";
|
||||
|
||||
std::unique_ptr<OTABackend> make_ota_backend() { return make_unique<ArduinoRP2040OTABackend>(); }
|
||||
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoRP2040OTABackend>(); }
|
||||
|
||||
OTAResponseTypes ArduinoRP2040OTABackend::begin(size_t image_size) {
|
||||
// Handle UPDATE_SIZE_UNKNOWN (0) by calculating available space
|
||||
if (image_size == 0) {
|
||||
// Similar to ESP8266, calculate available space from flash layout
|
||||
extern uint8_t _FS_start;
|
||||
extern uint8_t _FS_end;
|
||||
// Calculate the size of the filesystem area which will be used for OTA
|
||||
size_t fs_size = &_FS_end - &_FS_start;
|
||||
// Reserve some space for filesystem overhead
|
||||
image_size = (fs_size - 0x1000) & 0xFFFFF000;
|
||||
ESP_LOGD(TAG, "OTA size unknown, using filesystem size: %u bytes", image_size);
|
||||
}
|
||||
// OTA size of 0 is not currently handled, but
|
||||
// web_server is not supported for RP2040, so this is not an issue.
|
||||
bool ret = Update.begin(image_size, U_FLASH);
|
||||
if (ret) {
|
||||
rp2040::preferences_prevent_write(true);
|
||||
@@ -84,7 +75,7 @@ void ArduinoRP2040OTABackend::abort() {
|
||||
rp2040::preferences_prevent_write(false);
|
||||
}
|
||||
|
||||
} // namespace ota_base
|
||||
} // namespace ota
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_RP2040
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "esphome/core/macros.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ota_base {
|
||||
namespace ota {
|
||||
|
||||
class ArduinoRP2040OTABackend : public OTABackend {
|
||||
public:
|
||||
@@ -22,7 +22,7 @@ class ArduinoRP2040OTABackend : public OTABackend {
|
||||
bool md5_set_{false};
|
||||
};
|
||||
|
||||
} // namespace ota_base
|
||||
} // namespace ota
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_RP2040
|
||||
@@ -9,9 +9,9 @@
|
||||
#include <spi_flash_mmap.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace ota_base {
|
||||
namespace ota {
|
||||
|
||||
std::unique_ptr<OTABackend> make_ota_backend() { return make_unique<IDFOTABackend>(); }
|
||||
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::IDFOTABackend>(); }
|
||||
|
||||
OTAResponseTypes IDFOTABackend::begin(size_t image_size) {
|
||||
this->partition_ = esp_ota_get_next_update_partition(nullptr);
|
||||
@@ -105,6 +105,6 @@ void IDFOTABackend::abort() {
|
||||
this->update_handle_ = 0;
|
||||
}
|
||||
|
||||
} // namespace ota_base
|
||||
} // namespace ota
|
||||
} // namespace esphome
|
||||
#endif
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <esp_ota_ops.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace ota_base {
|
||||
namespace ota {
|
||||
|
||||
class IDFOTABackend : public OTABackend {
|
||||
public:
|
||||
@@ -27,6 +27,6 @@ class IDFOTABackend : public OTABackend {
|
||||
bool md5_set_{false};
|
||||
};
|
||||
|
||||
} // namespace ota_base
|
||||
} // namespace ota
|
||||
} // namespace esphome
|
||||
#endif
|
||||
@@ -1,23 +0,0 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
AUTO_LOAD = ["md5"]
|
||||
|
||||
ota_base_ns = cg.esphome_ns.namespace("ota_base")
|
||||
OTAComponent = ota_base_ns.class_("OTAComponent", cg.Component)
|
||||
OTAState = ota_base_ns.enum("OTAState")
|
||||
|
||||
|
||||
@coroutine_with_priority(52.0)
|
||||
async def to_code(config):
|
||||
# Note: USE_OTA_STATE_CALLBACK is not defined here
|
||||
# Components that need OTA callbacks (like esp32_ble_tracker, speaker, etc.)
|
||||
# define USE_OTA_STATE_CALLBACK themselves in their own __init__.py files
|
||||
# This ensures the callback functionality is only compiled when actually needed
|
||||
|
||||
if CORE.is_esp32 and CORE.using_arduino:
|
||||
cg.add_library("Update", None)
|
||||
|
||||
if CORE.is_rp2040 and CORE.using_arduino:
|
||||
cg.add_library("Updater", None)
|
||||
@@ -1,26 +0,0 @@
|
||||
"""
|
||||
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]))
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#include "esphome/components/audio/audio.h"
|
||||
#ifdef USE_OTA
|
||||
#include "esphome/components/ota_base/ota_backend.h"
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
@@ -67,16 +67,16 @@ void SpeakerMediaPlayer::setup() {
|
||||
}
|
||||
|
||||
#ifdef USE_OTA
|
||||
ota_base::get_global_ota_callback()->add_on_state_callback(
|
||||
[this](ota_base::OTAState state, float progress, uint8_t error, ota_base::OTAComponent *comp) {
|
||||
if (state == ota_base::OTA_STARTED) {
|
||||
ota::get_global_ota_callback()->add_on_state_callback(
|
||||
[this](ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) {
|
||||
if (state == ota::OTA_STARTED) {
|
||||
if (this->media_pipeline_ != nullptr) {
|
||||
this->media_pipeline_->suspend_tasks();
|
||||
}
|
||||
if (this->announcement_pipeline_ != nullptr) {
|
||||
this->announcement_pipeline_->suspend_tasks();
|
||||
}
|
||||
} else if (state == ota_base::OTA_ERROR) {
|
||||
} else if (state == ota::OTA_ERROR) {
|
||||
if (this->media_pipeline_ != nullptr) {
|
||||
this->media_pipeline_->resume_tasks();
|
||||
}
|
||||
|
||||
@@ -33,8 +33,9 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
AUTO_LOAD = ["json", "web_server_base", "ota_base"]
|
||||
AUTO_LOAD = ["json", "web_server_base"]
|
||||
|
||||
CONF_SORTING_GROUP_ID = "sorting_group_id"
|
||||
CONF_SORTING_GROUPS = "sorting_groups"
|
||||
@@ -47,7 +48,7 @@ WebServer = web_server_ns.class_("WebServer", cg.Component, cg.Controller)
|
||||
sorting_groups = {}
|
||||
|
||||
|
||||
def default_url(config):
|
||||
def default_url(config: ConfigType) -> ConfigType:
|
||||
config = config.copy()
|
||||
if config[CONF_VERSION] == 1:
|
||||
if CONF_CSS_URL not in config:
|
||||
@@ -67,13 +68,27 @@ def default_url(config):
|
||||
return config
|
||||
|
||||
|
||||
def validate_local(config):
|
||||
def validate_local(config: ConfigType) -> ConfigType:
|
||||
if CONF_LOCAL in config and config[CONF_VERSION] == 1:
|
||||
raise cv.Invalid("'local' is not supported in version 1")
|
||||
return config
|
||||
|
||||
|
||||
def validate_sorting_groups(config):
|
||||
def validate_ota_removed(config: ConfigType) -> ConfigType:
|
||||
# Only raise error if OTA is explicitly enabled (True)
|
||||
# If it's False or not specified, we can safely ignore it
|
||||
if config.get(CONF_OTA):
|
||||
raise cv.Invalid(
|
||||
f"The '{CONF_OTA}' option has been removed from 'web_server'. "
|
||||
f"Please use the new OTA platform structure instead:\n\n"
|
||||
f"ota:\n"
|
||||
f" - platform: web_server\n\n"
|
||||
f"See https://esphome.io/components/ota for more information."
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
def validate_sorting_groups(config: ConfigType) -> ConfigType:
|
||||
if CONF_SORTING_GROUPS in config and config[CONF_VERSION] != 3:
|
||||
raise cv.Invalid(
|
||||
f"'{CONF_SORTING_GROUPS}' is only supported in 'web_server' version 3"
|
||||
@@ -84,7 +99,7 @@ def validate_sorting_groups(config):
|
||||
def _validate_no_sorting_component(
|
||||
sorting_component: str,
|
||||
webserver_version: int,
|
||||
config: dict,
|
||||
config: ConfigType,
|
||||
path: list[str] | None = None,
|
||||
) -> None:
|
||||
if path is None:
|
||||
@@ -107,7 +122,7 @@ def _validate_no_sorting_component(
|
||||
)
|
||||
|
||||
|
||||
def _final_validate_sorting(config):
|
||||
def _final_validate_sorting(config: ConfigType) -> ConfigType:
|
||||
if (webserver_version := config.get(CONF_VERSION)) != 3:
|
||||
_validate_no_sorting_component(
|
||||
CONF_SORTING_WEIGHT, webserver_version, fv.full_config.get()
|
||||
@@ -170,7 +185,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
web_server_base.WebServerBase
|
||||
),
|
||||
cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean,
|
||||
cv.Optional(CONF_OTA, default=True): cv.boolean,
|
||||
cv.Optional(CONF_OTA, default=False): cv.boolean,
|
||||
cv.Optional(CONF_LOG, default=True): cv.boolean,
|
||||
cv.Optional(CONF_LOCAL): cv.boolean,
|
||||
cv.Optional(CONF_SORTING_GROUPS): cv.ensure_list(sorting_group),
|
||||
@@ -188,6 +203,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
default_url,
|
||||
validate_local,
|
||||
validate_sorting_groups,
|
||||
validate_ota_removed,
|
||||
)
|
||||
|
||||
|
||||
@@ -271,11 +287,8 @@ async def to_code(config):
|
||||
else:
|
||||
cg.add(var.set_css_url(config[CONF_CSS_URL]))
|
||||
cg.add(var.set_js_url(config[CONF_JS_URL]))
|
||||
cg.add(var.set_allow_ota(config[CONF_OTA]))
|
||||
if config[CONF_OTA]:
|
||||
# Define USE_WEBSERVER_OTA based only on web_server OTA config
|
||||
# Web server OTA now uses ota_base backend for consistency
|
||||
cg.add_define("USE_WEBSERVER_OTA")
|
||||
# OTA is now handled by the web_server OTA platform
|
||||
# The CONF_OTA option is kept only for backwards compatibility validation
|
||||
cg.add(var.set_expose_log(config[CONF_LOG]))
|
||||
if config[CONF_ENABLE_PRIVATE_NETWORK_ACCESS]:
|
||||
cg.add_define("USE_WEBSERVER_PRIVATE_NETWORK_ACCESS")
|
||||
|
||||
29
esphome/components/web_server/ota/__init__.py
Normal file
29
esphome/components/web_server/ota/__init__.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.core import coroutine_with_priority
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
DEPENDENCIES = ["network", "web_server_base"]
|
||||
|
||||
web_server_ns = cg.esphome_ns.namespace("web_server")
|
||||
WebServerOTAComponent = web_server_ns.class_("WebServerOTAComponent", OTAComponent)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(WebServerOTAComponent),
|
||||
}
|
||||
)
|
||||
.extend(BASE_OTA_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(52.0)
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await ota_to_code(var, config)
|
||||
await cg.register_component(var, config)
|
||||
cg.add_define("USE_WEBSERVER_OTA")
|
||||
201
esphome/components/web_server/ota/ota_web_server.cpp
Normal file
201
esphome/components/web_server/ota/ota_web_server.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
#include "ota_web_server.h"
|
||||
#ifdef USE_WEBSERVER_OTA
|
||||
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace web_server {
|
||||
|
||||
static const char *const TAG = "web_server.ota";
|
||||
|
||||
class OTARequestHandler : public AsyncWebHandler {
|
||||
public:
|
||||
OTARequestHandler(WebServerOTAComponent *parent) : parent_(parent) {}
|
||||
void handleRequest(AsyncWebServerRequest *request) override;
|
||||
void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len,
|
||||
bool final) override;
|
||||
bool canHandle(AsyncWebServerRequest *request) const override {
|
||||
return request->url() == "/update" && request->method() == HTTP_POST;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
bool isRequestHandlerTrivial() const override { return false; }
|
||||
|
||||
protected:
|
||||
void report_ota_progress_(AsyncWebServerRequest *request);
|
||||
void schedule_ota_reboot_();
|
||||
void ota_init_(const char *filename);
|
||||
|
||||
uint32_t last_ota_progress_{0};
|
||||
uint32_t ota_read_length_{0};
|
||||
WebServerOTAComponent *parent_;
|
||||
bool ota_success_{false};
|
||||
|
||||
private:
|
||||
std::unique_ptr<ota::OTABackend> ota_backend_{nullptr};
|
||||
};
|
||||
|
||||
void OTARequestHandler::report_ota_progress_(AsyncWebServerRequest *request) {
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_ota_progress_ > 1000) {
|
||||
float percentage = 0.0f;
|
||||
if (request->contentLength() != 0) {
|
||||
// Note: Using contentLength() for progress calculation is technically wrong as it includes
|
||||
// multipart headers/boundaries, but it's only off by a small amount and we don't have
|
||||
// access to the actual firmware size until the upload is complete. This is intentional
|
||||
// as it still gives the user a reasonable progress indication.
|
||||
percentage = (this->ota_read_length_ * 100.0f) / request->contentLength();
|
||||
ESP_LOGD(TAG, "OTA in progress: %0.1f%%", percentage);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "OTA in progress: %u bytes read", this->ota_read_length_);
|
||||
}
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
// Report progress - use call_deferred since we're in web server task
|
||||
this->parent_->state_callback_.call_deferred(ota::OTA_IN_PROGRESS, percentage, 0);
|
||||
#endif
|
||||
this->last_ota_progress_ = now;
|
||||
}
|
||||
}
|
||||
|
||||
void OTARequestHandler::schedule_ota_reboot_() {
|
||||
ESP_LOGI(TAG, "OTA update successful!");
|
||||
this->parent_->set_timeout(100, []() {
|
||||
ESP_LOGI(TAG, "Performing OTA reboot now");
|
||||
App.safe_reboot();
|
||||
});
|
||||
}
|
||||
|
||||
void OTARequestHandler::ota_init_(const char *filename) {
|
||||
ESP_LOGI(TAG, "OTA Update Start: %s", filename);
|
||||
this->ota_read_length_ = 0;
|
||||
this->ota_success_ = false;
|
||||
}
|
||||
|
||||
void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index,
|
||||
uint8_t *data, size_t len, bool final) {
|
||||
ota::OTAResponseTypes error_code = ota::OTA_RESPONSE_OK;
|
||||
|
||||
if (index == 0 && !this->ota_backend_) {
|
||||
// Initialize OTA on first call
|
||||
this->ota_init_(filename.c_str());
|
||||
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
// Notify OTA started - use call_deferred since we're in web server task
|
||||
this->parent_->state_callback_.call_deferred(ota::OTA_STARTED, 0.0f, 0);
|
||||
#endif
|
||||
|
||||
// Platform-specific pre-initialization
|
||||
#ifdef USE_ARDUINO
|
||||
#ifdef USE_ESP8266
|
||||
Update.runAsync(true);
|
||||
#endif
|
||||
#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_LIBRETINY)
|
||||
if (Update.isRunning()) {
|
||||
Update.abort();
|
||||
}
|
||||
#endif
|
||||
#endif // USE_ARDUINO
|
||||
|
||||
this->ota_backend_ = ota::make_ota_backend();
|
||||
if (!this->ota_backend_) {
|
||||
ESP_LOGE(TAG, "Failed to create OTA backend");
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f,
|
||||
static_cast<uint8_t>(ota::OTA_RESPONSE_ERROR_UNKNOWN));
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// Web server OTA uses multipart uploads where the actual firmware size
|
||||
// is unknown (contentLength includes multipart overhead)
|
||||
// Pass 0 to indicate unknown size
|
||||
error_code = this->ota_backend_->begin(0);
|
||||
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||
ESP_LOGE(TAG, "OTA begin failed: %d", error_code);
|
||||
this->ota_backend_.reset();
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->ota_backend_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Process data
|
||||
if (len > 0) {
|
||||
error_code = this->ota_backend_->write(data, len);
|
||||
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||
ESP_LOGE(TAG, "OTA write failed: %d", error_code);
|
||||
this->ota_backend_->abort();
|
||||
this->ota_backend_.reset();
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
this->ota_read_length_ += len;
|
||||
this->report_ota_progress_(request);
|
||||
}
|
||||
|
||||
// Finalize
|
||||
if (final) {
|
||||
ESP_LOGD(TAG, "OTA final chunk: index=%u, len=%u, total_read=%u, contentLength=%u", index, len,
|
||||
this->ota_read_length_, request->contentLength());
|
||||
|
||||
// For Arduino framework, the Update library tracks expected size from firmware header
|
||||
// If we haven't received enough data, calling end() will fail
|
||||
// This can happen if the upload is interrupted or the client disconnects
|
||||
error_code = this->ota_backend_->end();
|
||||
if (error_code == ota::OTA_RESPONSE_OK) {
|
||||
this->ota_success_ = true;
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
// Report completion before reboot - use call_deferred since we're in web server task
|
||||
this->parent_->state_callback_.call_deferred(ota::OTA_COMPLETED, 100.0f, 0);
|
||||
#endif
|
||||
this->schedule_ota_reboot_();
|
||||
} else {
|
||||
ESP_LOGE(TAG, "OTA end failed: %d", error_code);
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
|
||||
#endif
|
||||
}
|
||||
this->ota_backend_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) {
|
||||
AsyncWebServerResponse *response;
|
||||
// Use the ota_success_ flag to determine the actual result
|
||||
const char *msg = this->ota_success_ ? "Update Successful!" : "Update Failed!";
|
||||
response = request->beginResponse(200, "text/plain", msg);
|
||||
response->addHeader("Connection", "close");
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void WebServerOTAComponent::setup() {
|
||||
// Get the global web server base instance and register our handler
|
||||
auto *base = web_server_base::global_web_server_base;
|
||||
if (base == nullptr) {
|
||||
ESP_LOGE(TAG, "WebServerBase not found. WebServer OTA requires web_server_base component");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
base->add_handler(new OTARequestHandler(this)); // NOLINT
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
// Register with global OTA callback system
|
||||
ota::register_ota_platform(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
void WebServerOTAComponent::dump_config() { ESP_LOGCONFIG(TAG, "Web Server OTA"); }
|
||||
|
||||
} // namespace web_server
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_WEBSERVER_OTA
|
||||
26
esphome/components/web_server/ota/ota_web_server.h
Normal file
26
esphome/components/web_server/ota/ota_web_server.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_WEBSERVER_OTA
|
||||
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#include "esphome/components/web_server_base/web_server_base.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace web_server {
|
||||
|
||||
class WebServerOTAComponent : public ota::OTAComponent {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
protected:
|
||||
friend class OTARequestHandler;
|
||||
};
|
||||
|
||||
} // namespace web_server
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_WEBSERVER_OTA
|
||||
@@ -273,7 +273,11 @@ std::string WebServer::get_config_json() {
|
||||
return json::build_json([this](JsonObject root) {
|
||||
root["title"] = App.get_friendly_name().empty() ? App.get_name() : App.get_friendly_name();
|
||||
root["comment"] = App.get_comment();
|
||||
root["ota"] = this->allow_ota_;
|
||||
#ifdef USE_WEBSERVER_OTA
|
||||
root["ota"] = true; // web_server OTA platform is configured
|
||||
#else
|
||||
root["ota"] = false;
|
||||
#endif
|
||||
root["log"] = this->expose_log_;
|
||||
root["lang"] = "en";
|
||||
});
|
||||
@@ -299,10 +303,7 @@ void WebServer::setup() {
|
||||
#endif
|
||||
this->base_->add_handler(this);
|
||||
|
||||
#ifdef USE_WEBSERVER_OTA
|
||||
if (this->allow_ota_)
|
||||
this->base_->add_ota_handler();
|
||||
#endif
|
||||
// OTA is now handled by the web_server OTA platform
|
||||
|
||||
// doesn't need defer functionality - if the queue is full, the client JS knows it's alive because it's clearly
|
||||
// getting a lot of events
|
||||
|
||||
@@ -212,11 +212,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
* @param include_internal Whether internal components should be displayed.
|
||||
*/
|
||||
void set_include_internal(bool include_internal) { include_internal_ = include_internal; }
|
||||
/** Set whether or not the webserver should expose the OTA form and handler.
|
||||
*
|
||||
* @param allow_ota.
|
||||
*/
|
||||
void set_allow_ota(bool allow_ota) { this->allow_ota_ = allow_ota; }
|
||||
/** Set whether or not the webserver should expose the Log.
|
||||
*
|
||||
* @param expose_log.
|
||||
@@ -525,7 +520,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
#ifdef USE_WEBSERVER_JS_INCLUDE
|
||||
const char *js_include_{nullptr};
|
||||
#endif
|
||||
bool allow_ota_{true};
|
||||
bool expose_log_{true};
|
||||
#ifdef USE_ESP32
|
||||
std::deque<std::function<void()>> to_schedule_;
|
||||
|
||||
@@ -192,11 +192,10 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
||||
|
||||
stream->print(F("</tbody></table><p>See <a href=\"https://esphome.io/web-api/index.html\">ESPHome Web API</a> for "
|
||||
"REST API documentation.</p>"));
|
||||
if (this->allow_ota_) {
|
||||
stream->print(
|
||||
F("<h2>OTA Update</h2><form method=\"POST\" action=\"/update\" enctype=\"multipart/form-data\"><input "
|
||||
"type=\"file\" name=\"update\"><input type=\"submit\" value=\"Update\"></form>"));
|
||||
}
|
||||
#ifdef USE_WEBSERVER_OTA
|
||||
stream->print(F("<h2>OTA Update</h2><form method=\"POST\" action=\"/update\" enctype=\"multipart/form-data\"><input "
|
||||
"type=\"file\" name=\"update\"><input type=\"submit\" value=\"Update\"></form>"));
|
||||
#endif
|
||||
stream->print(F("<h2>Debug Log</h2><pre id=\"log\"></pre>"));
|
||||
#ifdef USE_WEBSERVER_JS_INCLUDE
|
||||
if (this->js_include_ != nullptr) {
|
||||
|
||||
@@ -30,6 +30,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
cg.add(cg.RawExpression(f"{web_server_base_ns}::global_web_server_base = {var}"))
|
||||
|
||||
if CORE.using_arduino:
|
||||
if CORE.is_esp32:
|
||||
@@ -39,4 +40,7 @@ async def to_code(config):
|
||||
if CORE.is_esp8266:
|
||||
cg.add_library("ESP8266WiFi", None)
|
||||
# https://github.com/ESP32Async/ESPAsyncWebServer/blob/main/library.json
|
||||
cg.add_library("ESP32Async/ESPAsyncWebServer", "3.7.8")
|
||||
# Use fork with libretiny compatibility fix
|
||||
cg.add_library(
|
||||
"https://github.com/bdraco/ESPAsyncWebServer.git#libretiny_Fix", None
|
||||
)
|
||||
|
||||
@@ -4,23 +4,13 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_WEBSERVER_OTA
|
||||
#include "esphome/components/ota_base/ota_backend.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#ifdef USE_ESP8266
|
||||
#include <Updater.h>
|
||||
#elif defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
#include <Update.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace web_server_base {
|
||||
|
||||
static const char *const TAG = "web_server_base";
|
||||
|
||||
WebServerBase *global_web_server_base = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
void WebServerBase::add_handler(AsyncWebHandler *handler) {
|
||||
// remove all handlers
|
||||
|
||||
@@ -33,156 +23,6 @@ void WebServerBase::add_handler(AsyncWebHandler *handler) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_WEBSERVER_OTA
|
||||
void OTARequestHandler::report_ota_progress_(AsyncWebServerRequest *request) {
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_ota_progress_ > 1000) {
|
||||
float percentage = 0.0f;
|
||||
if (request->contentLength() != 0) {
|
||||
// Note: Using contentLength() for progress calculation is technically wrong as it includes
|
||||
// multipart headers/boundaries, but it's only off by a small amount and we don't have
|
||||
// access to the actual firmware size until the upload is complete. This is intentional
|
||||
// as it still gives the user a reasonable progress indication.
|
||||
percentage = (this->ota_read_length_ * 100.0f) / request->contentLength();
|
||||
ESP_LOGD(TAG, "OTA in progress: %0.1f%%", percentage);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "OTA in progress: %u bytes read", this->ota_read_length_);
|
||||
}
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
// Report progress - use call_deferred since we're in web server task
|
||||
this->parent_->state_callback_.call_deferred(ota_base::OTA_IN_PROGRESS, percentage, 0);
|
||||
#endif
|
||||
this->last_ota_progress_ = now;
|
||||
}
|
||||
}
|
||||
|
||||
void OTARequestHandler::schedule_ota_reboot_() {
|
||||
ESP_LOGI(TAG, "OTA update successful!");
|
||||
this->parent_->set_timeout(100, []() {
|
||||
ESP_LOGI(TAG, "Performing OTA reboot now");
|
||||
App.safe_reboot();
|
||||
});
|
||||
}
|
||||
|
||||
void OTARequestHandler::ota_init_(const char *filename) {
|
||||
ESP_LOGI(TAG, "OTA Update Start: %s", filename);
|
||||
this->ota_read_length_ = 0;
|
||||
this->ota_success_ = false;
|
||||
}
|
||||
|
||||
void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index,
|
||||
uint8_t *data, size_t len, bool final) {
|
||||
ota_base::OTAResponseTypes error_code = ota_base::OTA_RESPONSE_OK;
|
||||
|
||||
if (index == 0 && !this->ota_backend_) {
|
||||
// Initialize OTA on first call
|
||||
this->ota_init_(filename.c_str());
|
||||
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
// Notify OTA started - use call_deferred since we're in web server task
|
||||
this->parent_->state_callback_.call_deferred(ota_base::OTA_STARTED, 0.0f, 0);
|
||||
#endif
|
||||
|
||||
// Platform-specific pre-initialization
|
||||
#ifdef USE_ARDUINO
|
||||
#ifdef USE_ESP8266
|
||||
Update.runAsync(true);
|
||||
#endif
|
||||
#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_LIBRETINY)
|
||||
if (Update.isRunning()) {
|
||||
Update.abort();
|
||||
}
|
||||
#endif
|
||||
#endif // USE_ARDUINO
|
||||
|
||||
this->ota_backend_ = ota_base::make_ota_backend();
|
||||
if (!this->ota_backend_) {
|
||||
ESP_LOGE(TAG, "Failed to create OTA backend");
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->parent_->state_callback_.call_deferred(ota_base::OTA_ERROR, 0.0f,
|
||||
static_cast<uint8_t>(ota_base::OTA_RESPONSE_ERROR_UNKNOWN));
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// Web server OTA uses multipart uploads where the actual firmware size
|
||||
// is unknown (contentLength includes multipart overhead)
|
||||
// Pass 0 to indicate unknown size
|
||||
error_code = this->ota_backend_->begin(0);
|
||||
if (error_code != ota_base::OTA_RESPONSE_OK) {
|
||||
ESP_LOGE(TAG, "OTA begin failed: %d", error_code);
|
||||
this->ota_backend_.reset();
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->parent_->state_callback_.call_deferred(ota_base::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->ota_backend_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Process data
|
||||
if (len > 0) {
|
||||
error_code = this->ota_backend_->write(data, len);
|
||||
if (error_code != ota_base::OTA_RESPONSE_OK) {
|
||||
ESP_LOGE(TAG, "OTA write failed: %d", error_code);
|
||||
this->ota_backend_->abort();
|
||||
this->ota_backend_.reset();
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->parent_->state_callback_.call_deferred(ota_base::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
this->ota_read_length_ += len;
|
||||
this->report_ota_progress_(request);
|
||||
}
|
||||
|
||||
// Finalize
|
||||
if (final) {
|
||||
ESP_LOGD(TAG, "OTA final chunk: index=%u, len=%u, total_read=%u, contentLength=%u", index, len,
|
||||
this->ota_read_length_, request->contentLength());
|
||||
|
||||
// For Arduino framework, the Update library tracks expected size from firmware header
|
||||
// If we haven't received enough data, calling end() will fail
|
||||
// This can happen if the upload is interrupted or the client disconnects
|
||||
error_code = this->ota_backend_->end();
|
||||
if (error_code == ota_base::OTA_RESPONSE_OK) {
|
||||
this->ota_success_ = true;
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
// Report completion before reboot - use call_deferred since we're in web server task
|
||||
this->parent_->state_callback_.call_deferred(ota_base::OTA_COMPLETED, 100.0f, 0);
|
||||
#endif
|
||||
this->schedule_ota_reboot_();
|
||||
} else {
|
||||
ESP_LOGE(TAG, "OTA end failed: %d", error_code);
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->parent_->state_callback_.call_deferred(ota_base::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
|
||||
#endif
|
||||
}
|
||||
this->ota_backend_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) {
|
||||
AsyncWebServerResponse *response;
|
||||
// Use the ota_success_ flag to determine the actual result
|
||||
const char *msg = this->ota_success_ ? "Update Successful!" : "Update Failed!";
|
||||
response = request->beginResponse(200, "text/plain", msg);
|
||||
response->addHeader("Connection", "close");
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void WebServerBase::add_ota_handler() {
|
||||
this->add_handler(new OTARequestHandler(this)); // NOLINT
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
// Register with global OTA callback system
|
||||
ota_base::register_ota_platform(this);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
float WebServerBase::get_setup_priority() const {
|
||||
// Before WiFi (captive portal)
|
||||
return setup_priority::WIFI + 2.0f;
|
||||
|
||||
@@ -14,13 +14,12 @@
|
||||
#include "esphome/components/web_server_idf/web_server_idf.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_WEBSERVER_OTA
|
||||
#include "esphome/components/ota_base/ota_backend.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace web_server_base {
|
||||
|
||||
class WebServerBase;
|
||||
extern WebServerBase *global_web_server_base; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
namespace internal {
|
||||
|
||||
class MiddlewareHandler : public AsyncWebHandler {
|
||||
@@ -83,11 +82,7 @@ class AuthMiddlewareHandler : public MiddlewareHandler {
|
||||
|
||||
} // namespace internal
|
||||
|
||||
#ifdef USE_WEBSERVER_OTA
|
||||
class WebServerBase : public ota_base::OTAComponent {
|
||||
#else
|
||||
class WebServerBase : public Component {
|
||||
#endif
|
||||
public:
|
||||
void init() {
|
||||
if (this->initialized_) {
|
||||
@@ -118,18 +113,10 @@ class WebServerBase : public Component {
|
||||
|
||||
void add_handler(AsyncWebHandler *handler);
|
||||
|
||||
#ifdef USE_WEBSERVER_OTA
|
||||
void add_ota_handler();
|
||||
#endif
|
||||
|
||||
void set_port(uint16_t port) { port_ = port; }
|
||||
uint16_t get_port() const { return port_; }
|
||||
|
||||
protected:
|
||||
#ifdef USE_WEBSERVER_OTA
|
||||
friend class OTARequestHandler;
|
||||
#endif
|
||||
|
||||
int initialized_{0};
|
||||
uint16_t port_{80};
|
||||
std::shared_ptr<AsyncWebServer> server_{nullptr};
|
||||
@@ -137,35 +124,6 @@ class WebServerBase : public Component {
|
||||
internal::Credentials credentials_;
|
||||
};
|
||||
|
||||
#ifdef USE_WEBSERVER_OTA
|
||||
class OTARequestHandler : public AsyncWebHandler {
|
||||
public:
|
||||
OTARequestHandler(WebServerBase *parent) : parent_(parent) {}
|
||||
void handleRequest(AsyncWebServerRequest *request) override;
|
||||
void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len,
|
||||
bool final) override;
|
||||
bool canHandle(AsyncWebServerRequest *request) const override {
|
||||
return request->url() == "/update" && request->method() == HTTP_POST;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
bool isRequestHandlerTrivial() const override { return false; }
|
||||
|
||||
protected:
|
||||
void report_ota_progress_(AsyncWebServerRequest *request);
|
||||
void schedule_ota_reboot_();
|
||||
void ota_init_(const char *filename);
|
||||
|
||||
uint32_t last_ota_progress_{0};
|
||||
uint32_t ota_read_length_{0};
|
||||
WebServerBase *parent_;
|
||||
bool ota_success_{false};
|
||||
|
||||
private:
|
||||
std::unique_ptr<ota_base::OTABackend> ota_backend_{nullptr};
|
||||
};
|
||||
#endif // USE_WEBSERVER_OTA
|
||||
|
||||
} // namespace web_server_base
|
||||
} // namespace esphome
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from esphome.components.esp32 import add_idf_component, add_idf_sdkconfig_option
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_OTA, CONF_WEB_SERVER
|
||||
from esphome.const import CONF_OTA
|
||||
from esphome.core import CORE
|
||||
|
||||
CODEOWNERS = ["@dentra"]
|
||||
@@ -14,7 +14,12 @@ CONFIG_SCHEMA = cv.All(
|
||||
async def to_code(config):
|
||||
# Increase the maximum supported size of headers section in HTTP request packet to be processed by the server
|
||||
add_idf_sdkconfig_option("CONFIG_HTTPD_MAX_REQ_HDR_LEN", 1024)
|
||||
# Check if web_server component has OTA enabled
|
||||
if CORE.config.get(CONF_WEB_SERVER, {}).get(CONF_OTA, True):
|
||||
# Add multipart parser component for ESP-IDF OTA support
|
||||
|
||||
# Check if web_server OTA platform is configured
|
||||
ota_config = CORE.config.get(CONF_OTA, [])
|
||||
has_web_server_ota = any(
|
||||
platform.get("platform") == "web_server" for platform in ota_config
|
||||
)
|
||||
if has_web_server_ota:
|
||||
# Add multipart parser dependency for web server OTA
|
||||
add_idf_component(name="zorxx/multipart-parser", ref="1.0.1")
|
||||
|
||||
@@ -84,10 +84,6 @@ void Application::setup() {
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "setup() finished successfully!");
|
||||
|
||||
// Clear setup priority overrides to free memory
|
||||
clear_setup_priority_overrides();
|
||||
|
||||
this->schedule_dump_config();
|
||||
this->calculate_looping_components_();
|
||||
}
|
||||
@@ -141,10 +137,6 @@ void Application::loop() {
|
||||
this->in_loop_ = false;
|
||||
this->app_state_ = new_app_state;
|
||||
|
||||
// Process any pending runtime stats printing after all components have run
|
||||
// This ensures stats printing doesn't affect component timing measurements
|
||||
runtime_stats.process_pending_stats(last_op_end_time);
|
||||
|
||||
// Use the last component's end time instead of calling millis() again
|
||||
auto elapsed = last_op_end_time - this->last_loop_;
|
||||
if (elapsed >= this->loop_interval_ || HighFrequencyLoopRequester::is_high_frequency()) {
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#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_DEVICES
|
||||
@@ -349,18 +348,6 @@ class Application {
|
||||
|
||||
uint32_t get_loop_interval() const { return static_cast<uint32_t>(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);
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
#include <cinttypes>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
@@ -14,30 +12,6 @@ namespace esphome {
|
||||
|
||||
static const char *const TAG = "component";
|
||||
|
||||
// Global vectors for component data that doesn't belong in every instance.
|
||||
// Using vector instead of unordered_map for both because:
|
||||
// - Much lower memory overhead (8 bytes per entry vs 20+ for unordered_map)
|
||||
// - Linear search is fine for small n (typically < 5 entries)
|
||||
// - These are rarely accessed (setup only or error cases only)
|
||||
|
||||
// Component error messages - only stores messages for failed components
|
||||
// Lazy allocated since most configs have zero failures
|
||||
// Note: We don't clear this vector because:
|
||||
// 1. Components are never destroyed in ESPHome
|
||||
// 2. Failed components remain failed (no recovery mechanism)
|
||||
// 3. Memory usage is minimal (only failures with custom messages are stored)
|
||||
static std::unique_ptr<std::vector<std::pair<const Component *, const char *>>> &get_component_error_messages() {
|
||||
static std::unique_ptr<std::vector<std::pair<const Component *, const char *>>> instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Setup priority overrides - freed after setup completes
|
||||
// Typically < 5 entries, lazy allocated
|
||||
static std::unique_ptr<std::vector<std::pair<const Component *, float>>> &get_setup_priority_overrides() {
|
||||
static std::unique_ptr<std::vector<std::pair<const Component *, float>>> instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
namespace setup_priority {
|
||||
|
||||
const float BUS = 1000.0f;
|
||||
@@ -128,17 +102,8 @@ void Component::call_setup() { this->setup(); }
|
||||
void Component::call_dump_config() {
|
||||
this->dump_config();
|
||||
if (this->is_failed()) {
|
||||
// Look up error message from global vector
|
||||
const char *error_msg = "unspecified";
|
||||
if (get_component_error_messages()) {
|
||||
for (const auto &pair : *get_component_error_messages()) {
|
||||
if (pair.first == this) {
|
||||
error_msg = pair.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ESP_LOGE(TAG, " Component %s is marked FAILED: %s", this->get_component_source(), error_msg);
|
||||
ESP_LOGE(TAG, " Component %s is marked FAILED: %s", this->get_component_source(),
|
||||
this->error_message_ ? this->error_message_ : "unspecified");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,21 +245,8 @@ void Component::status_set_error(const char *message) {
|
||||
this->component_state_ |= STATUS_LED_ERROR;
|
||||
App.app_state_ |= STATUS_LED_ERROR;
|
||||
ESP_LOGE(TAG, "Component %s set Error flag: %s", this->get_component_source(), message);
|
||||
if (strcmp(message, "unspecified") != 0) {
|
||||
// Lazy allocate the error messages vector if needed
|
||||
if (!get_component_error_messages()) {
|
||||
get_component_error_messages() = std::make_unique<std::vector<std::pair<const Component *, const char *>>>();
|
||||
}
|
||||
// Check if this component already has an error message
|
||||
for (auto &pair : *get_component_error_messages()) {
|
||||
if (pair.first == this) {
|
||||
pair.second = message;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Add new error message
|
||||
get_component_error_messages()->emplace_back(this, message);
|
||||
}
|
||||
if (strcmp(message, "unspecified") != 0)
|
||||
this->error_message_ = message;
|
||||
}
|
||||
void Component::status_clear_warning() {
|
||||
if ((this->component_state_ & STATUS_LED_WARNING) == 0)
|
||||
@@ -318,36 +270,11 @@ void Component::status_momentary_error(const std::string &name, uint32_t length)
|
||||
}
|
||||
void Component::dump_config() {}
|
||||
float Component::get_actual_setup_priority() const {
|
||||
// Check if there's an override in the global vector
|
||||
if (get_setup_priority_overrides()) {
|
||||
// Linear search is fine for small n (typically < 5 overrides)
|
||||
for (const auto &pair : *get_setup_priority_overrides()) {
|
||||
if (pair.first == this) {
|
||||
return pair.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this->get_setup_priority();
|
||||
}
|
||||
void Component::set_setup_priority(float priority) {
|
||||
// Lazy allocate the vector if needed
|
||||
if (!get_setup_priority_overrides()) {
|
||||
get_setup_priority_overrides() = std::make_unique<std::vector<std::pair<const Component *, float>>>();
|
||||
// Reserve some space to avoid reallocations (most configs have < 10 overrides)
|
||||
get_setup_priority_overrides()->reserve(10);
|
||||
}
|
||||
|
||||
// Check if this component already has an override
|
||||
for (auto &pair : *get_setup_priority_overrides()) {
|
||||
if (pair.first == this) {
|
||||
pair.second = priority;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Add new override
|
||||
get_setup_priority_overrides()->emplace_back(this, priority);
|
||||
if (std::isnan(this->setup_priority_override_))
|
||||
return this->get_setup_priority();
|
||||
return this->setup_priority_override_;
|
||||
}
|
||||
void Component::set_setup_priority(float priority) { this->setup_priority_override_ = priority; }
|
||||
|
||||
bool Component::has_overridden_loop() const {
|
||||
#if defined(USE_HOST) || defined(CLANG_TIDY)
|
||||
@@ -392,9 +319,6 @@ uint32_t WarnIfComponentBlockingGuard::finish() {
|
||||
uint32_t curr_time = millis();
|
||||
|
||||
uint32_t blocking_time = curr_time - this->started_;
|
||||
|
||||
// Record component runtime stats
|
||||
runtime_stats.record_component_time(this->component_, blocking_time, curr_time);
|
||||
bool should_warn;
|
||||
if (this->component_ != nullptr) {
|
||||
should_warn = this->component_->should_warn_of_blocking(blocking_time);
|
||||
@@ -412,9 +336,4 @@ uint32_t WarnIfComponentBlockingGuard::finish() {
|
||||
|
||||
WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {}
|
||||
|
||||
void clear_setup_priority_overrides() {
|
||||
// Free the setup priority map completely
|
||||
get_setup_priority_overrides().reset();
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <string>
|
||||
|
||||
#include "esphome/core/optional.h"
|
||||
#include "esphome/core/runtime_stats.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -388,7 +387,9 @@ class Component {
|
||||
bool cancel_defer(const std::string &name); // NOLINT
|
||||
|
||||
// Ordered for optimal packing on 32-bit systems
|
||||
float setup_priority_override_{NAN};
|
||||
const char *component_source_{nullptr};
|
||||
const char *error_message_{nullptr};
|
||||
uint16_t warn_if_blocking_over_{WARN_IF_BLOCKING_OVER_MS}; ///< Warn if blocked for this many ms (max 65.5s)
|
||||
/// State of this component - each bit has a purpose:
|
||||
/// Bits 0-1: Component state (0x00=CONSTRUCTION, 0x01=SETUP, 0x02=LOOP, 0x03=FAILED)
|
||||
@@ -458,7 +459,4 @@ class WarnIfComponentBlockingGuard {
|
||||
Component *component_;
|
||||
};
|
||||
|
||||
// Function to clear setup priority overrides after all components are set up
|
||||
void clear_setup_priority_overrides();
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
#include "esphome/core/runtime_stats.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include <algorithm>
|
||||
|
||||
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;
|
||||
|
||||
// Check if we have cached the name for this component
|
||||
auto name_it = this->component_names_cache_.find(component);
|
||||
if (name_it == this->component_names_cache_.end()) {
|
||||
// First time seeing this component, cache its name
|
||||
const char *source = component->get_component_source();
|
||||
this->component_names_cache_[component] = source;
|
||||
this->component_stats_[source].record_time(duration_ms);
|
||||
} else {
|
||||
// Use cached name - no string operations, just map lookup
|
||||
this->component_stats_[name_it->second].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;
|
||||
}
|
||||
|
||||
// Don't print stats here anymore - let process_pending_stats handle it
|
||||
}
|
||||
|
||||
void RuntimeStatsCollector::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 RuntimeStatsCollector::process_pending_stats(uint32_t current_time) {
|
||||
if (!this->enabled_ || this->next_log_time_ == 0)
|
||||
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
|
||||
@@ -1,121 +0,0 @@
|
||||
#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);
|
||||
|
||||
// Process any pending stats printing (should be called after component loop)
|
||||
void process_pending_stats(uint32_t current_time);
|
||||
|
||||
protected:
|
||||
void log_stats_();
|
||||
|
||||
void reset_stats_() {
|
||||
for (auto &it : this->component_stats_) {
|
||||
it.second.reset_period_stats();
|
||||
}
|
||||
}
|
||||
|
||||
// Back to string keys, but we'll cache the source name per component
|
||||
std::map<std::string, ComponentRuntimeStats> component_stats_;
|
||||
std::map<Component *, std::string> component_names_cache_;
|
||||
uint32_t log_interval_;
|
||||
uint32_t next_log_time_;
|
||||
bool enabled_;
|
||||
};
|
||||
|
||||
// Global instance for runtime stats collection
|
||||
extern RuntimeStatsCollector runtime_stats;
|
||||
|
||||
} // namespace esphome
|
||||
@@ -526,13 +526,9 @@ class BytesType(TypeInfo):
|
||||
reference_type = "std::string &"
|
||||
const_reference_type = "const std::string &"
|
||||
decode_length = "value.as_string()"
|
||||
encode_func = "encode_bytes"
|
||||
encode_func = "encode_string"
|
||||
wire_type = WireType.LENGTH_DELIMITED # Uses wire type 2
|
||||
|
||||
@property
|
||||
def encode_content(self) -> str:
|
||||
return f"buffer.encode_bytes({self.number}, reinterpret_cast<const uint8_t*>(this->{self.field_name}.data()), this->{self.field_name}.size());"
|
||||
|
||||
def dump(self, name: str) -> str:
|
||||
o = f'out.append("\'").append({name}).append("\'");'
|
||||
return o
|
||||
|
||||
102
tests/component_tests/ota/test_web_server_ota.py
Normal file
102
tests/component_tests/ota/test_web_server_ota.py
Normal file
@@ -0,0 +1,102 @@
|
||||
"""Tests for the web_server OTA platform."""
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
|
||||
def test_web_server_ota_generated(generate_main: Callable[[str], str]) -> None:
|
||||
"""Test that web_server OTA platform generates correct code."""
|
||||
main_cpp = generate_main("tests/component_tests/ota/test_web_server_ota.yaml")
|
||||
|
||||
# Check that the web server OTA component is included
|
||||
assert "WebServerOTAComponent" in main_cpp
|
||||
assert "web_server::WebServerOTAComponent" in main_cpp
|
||||
|
||||
# Check that global web server base is referenced
|
||||
assert "global_web_server_base" in main_cpp
|
||||
|
||||
# Check component is registered
|
||||
assert "App.register_component(web_server_webserverotacomponent_id)" in main_cpp
|
||||
|
||||
|
||||
def test_web_server_ota_with_callbacks(generate_main: Callable[[str], str]) -> None:
|
||||
"""Test web_server OTA with state callbacks."""
|
||||
main_cpp = generate_main(
|
||||
"tests/component_tests/ota/test_web_server_ota_callbacks.yaml"
|
||||
)
|
||||
|
||||
# Check that web server OTA component is present
|
||||
assert "WebServerOTAComponent" in main_cpp
|
||||
|
||||
# Check that callbacks are configured
|
||||
# The actual callback code is in the component implementation, not main.cpp
|
||||
# But we can check that logger.log statements are present from the callbacks
|
||||
assert "logger.log" in main_cpp
|
||||
assert "OTA started" in main_cpp
|
||||
assert "OTA completed" in main_cpp
|
||||
assert "OTA error" in main_cpp
|
||||
|
||||
|
||||
def test_web_server_ota_idf_multipart(generate_main: Callable[[str], str]) -> None:
|
||||
"""Test that ESP-IDF builds include multipart parser dependency."""
|
||||
main_cpp = generate_main("tests/component_tests/ota/test_web_server_ota_idf.yaml")
|
||||
|
||||
# Check that web server OTA component is present
|
||||
assert "WebServerOTAComponent" in main_cpp
|
||||
|
||||
# For ESP-IDF builds, the framework type is esp-idf
|
||||
# The multipart parser dependency is added by web_server_idf
|
||||
assert "web_server::WebServerOTAComponent" in main_cpp
|
||||
|
||||
|
||||
def test_web_server_ota_without_web_server_fails(
|
||||
generate_main: Callable[[str], str],
|
||||
) -> None:
|
||||
"""Test that web_server OTA requires web_server component."""
|
||||
# This should fail during validation since web_server_base is required
|
||||
# but we can't test validation failures with generate_main
|
||||
# Instead, verify that both components are needed in valid config
|
||||
main_cpp = generate_main("tests/component_tests/ota/test_web_server_ota.yaml")
|
||||
|
||||
# Both web server and OTA components should be present
|
||||
assert "WebServer" in main_cpp
|
||||
assert "WebServerOTAComponent" in main_cpp
|
||||
|
||||
|
||||
def test_multiple_ota_platforms(generate_main: Callable[[str], str]) -> None:
|
||||
"""Test multiple OTA platforms can coexist."""
|
||||
main_cpp = generate_main("tests/component_tests/ota/test_web_server_ota_multi.yaml")
|
||||
|
||||
# Check all OTA platforms are included
|
||||
assert "WebServerOTAComponent" in main_cpp
|
||||
assert "ESPHomeOTAComponent" in main_cpp
|
||||
assert "OtaHttpRequestComponent" in main_cpp
|
||||
|
||||
# Check components are from correct namespaces
|
||||
assert "web_server::WebServerOTAComponent" in main_cpp
|
||||
assert "esphome::ESPHomeOTAComponent" in main_cpp
|
||||
assert "http_request::OtaHttpRequestComponent" in main_cpp
|
||||
|
||||
|
||||
def test_web_server_ota_arduino_with_auth(generate_main: Callable[[str], str]) -> None:
|
||||
"""Test web_server OTA with Arduino framework and authentication."""
|
||||
main_cpp = generate_main(
|
||||
"tests/component_tests/ota/test_web_server_ota_arduino.yaml"
|
||||
)
|
||||
|
||||
# Check web server OTA component is present
|
||||
assert "WebServerOTAComponent" in main_cpp
|
||||
|
||||
# Check authentication is set up for web server
|
||||
assert "set_auth_username" in main_cpp
|
||||
assert "set_auth_password" in main_cpp
|
||||
|
||||
|
||||
def test_web_server_ota_esp8266(generate_main: Callable[[str], str]) -> None:
|
||||
"""Test web_server OTA on ESP8266 platform."""
|
||||
main_cpp = generate_main(
|
||||
"tests/component_tests/ota/test_web_server_ota_esp8266.yaml"
|
||||
)
|
||||
|
||||
# Check web server OTA component is present
|
||||
assert "WebServerOTAComponent" in main_cpp
|
||||
assert "web_server::WebServerOTAComponent" in main_cpp
|
||||
15
tests/component_tests/ota/test_web_server_ota.yaml
Normal file
15
tests/component_tests/ota/test_web_server_ota.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
esphome:
|
||||
name: test_web_server_ota
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
web_server:
|
||||
port: 80
|
||||
|
||||
ota:
|
||||
- platform: web_server
|
||||
18
tests/component_tests/ota/test_web_server_ota_arduino.yaml
Normal file
18
tests/component_tests/ota/test_web_server_ota_arduino.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
esphome:
|
||||
name: test_web_server_ota_arduino
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
web_server:
|
||||
port: 80
|
||||
auth:
|
||||
username: admin
|
||||
password: admin
|
||||
|
||||
ota:
|
||||
- platform: web_server
|
||||
31
tests/component_tests/ota/test_web_server_ota_callbacks.yaml
Normal file
31
tests/component_tests/ota/test_web_server_ota_callbacks.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
esphome:
|
||||
name: test_web_server_ota_callbacks
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
logger:
|
||||
|
||||
web_server:
|
||||
port: 80
|
||||
|
||||
ota:
|
||||
- platform: web_server
|
||||
on_begin:
|
||||
- logger.log: "OTA started"
|
||||
on_progress:
|
||||
- logger.log:
|
||||
format: "OTA progress: %.1f%%"
|
||||
args: ["x"]
|
||||
on_end:
|
||||
- logger.log: "OTA completed"
|
||||
on_error:
|
||||
- logger.log:
|
||||
format: "OTA error: %d"
|
||||
args: ["x"]
|
||||
on_state_change:
|
||||
- logger.log: "OTA state changed"
|
||||
15
tests/component_tests/ota/test_web_server_ota_esp8266.yaml
Normal file
15
tests/component_tests/ota/test_web_server_ota_esp8266.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
esphome:
|
||||
name: test_web_server_ota_esp8266
|
||||
|
||||
esp8266:
|
||||
board: nodemcuv2
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
web_server:
|
||||
port: 80
|
||||
|
||||
ota:
|
||||
- platform: web_server
|
||||
17
tests/component_tests/ota/test_web_server_ota_idf.yaml
Normal file
17
tests/component_tests/ota/test_web_server_ota_idf.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
esphome:
|
||||
name: test_web_server_ota_idf
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
web_server:
|
||||
port: 80
|
||||
|
||||
ota:
|
||||
- platform: web_server
|
||||
21
tests/component_tests/ota/test_web_server_ota_multi.yaml
Normal file
21
tests/component_tests/ota/test_web_server_ota_multi.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
esphome:
|
||||
name: test_web_server_ota_multi
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
web_server:
|
||||
port: 80
|
||||
|
||||
http_request:
|
||||
verify_ssl: false
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: "test_password"
|
||||
- platform: web_server
|
||||
- platform: http_request
|
||||
38
tests/component_tests/web_server/test_ota_migration.py
Normal file
38
tests/component_tests/web_server/test_ota_migration.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""Tests for web_server OTA migration validation."""
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
|
||||
def test_web_server_ota_true_fails_validation() -> None:
|
||||
"""Test that web_server with ota: true fails validation with helpful message."""
|
||||
from esphome.components.web_server import validate_ota_removed
|
||||
|
||||
# Config with ota: true should fail
|
||||
config: ConfigType = {"ota": True}
|
||||
|
||||
with pytest.raises(cv.Invalid) as exc_info:
|
||||
validate_ota_removed(config)
|
||||
|
||||
# Check error message contains migration instructions
|
||||
error_msg = str(exc_info.value)
|
||||
assert "has been removed from 'web_server'" in error_msg
|
||||
assert "platform: web_server" in error_msg
|
||||
assert "ota:" in error_msg
|
||||
|
||||
|
||||
def test_web_server_ota_false_passes_validation() -> None:
|
||||
"""Test that web_server with ota: false passes validation."""
|
||||
from esphome.components.web_server import validate_ota_removed
|
||||
|
||||
# Config with ota: false should pass
|
||||
config: ConfigType = {"ota": False}
|
||||
result = validate_ota_removed(config)
|
||||
assert result == config
|
||||
|
||||
# Config without ota should also pass
|
||||
config: ConfigType = {}
|
||||
result = validate_ota_removed(config)
|
||||
assert result == config
|
||||
@@ -1,10 +0,0 @@
|
||||
# Test that ota_base compiles correctly as a dependency
|
||||
# This component is typically auto-loaded by other components
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: "test1234"
|
||||
@@ -1 +0,0 @@
|
||||
<<: !include common.yaml
|
||||
@@ -1,3 +1,11 @@
|
||||
esphome:
|
||||
name: test-web-server-no-ota-idf
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
packages:
|
||||
device_base: !include common.yaml
|
||||
|
||||
@@ -6,4 +14,3 @@ packages:
|
||||
web_server:
|
||||
port: 8080
|
||||
version: 2
|
||||
ota: false
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# Test configuration for ESP-IDF web server with OTA enabled
|
||||
esphome:
|
||||
name: test-web-server-ota-idf
|
||||
|
||||
# Force ESP-IDF framework
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
@@ -15,17 +13,17 @@ packages:
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: "test_ota_password"
|
||||
- platform: web_server
|
||||
|
||||
# Web server with OTA enabled
|
||||
# Web server configuration
|
||||
web_server:
|
||||
port: 8080
|
||||
version: 2
|
||||
ota: true
|
||||
include_internal: true
|
||||
|
||||
# Enable debug logging for OTA
|
||||
logger:
|
||||
level: DEBUG
|
||||
level: VERBOSE
|
||||
logs:
|
||||
web_server: VERBOSE
|
||||
web_server_idf: VERBOSE
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
esphome:
|
||||
name: test-ws-ota-disabled-idf
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
packages:
|
||||
device_base: !include common.yaml
|
||||
|
||||
# OTA is configured but web_server OTA is disabled
|
||||
# OTA is configured but web_server OTA is NOT included
|
||||
ota:
|
||||
- platform: esphome
|
||||
|
||||
web_server:
|
||||
port: 8080
|
||||
version: 2
|
||||
ota: false
|
||||
|
||||
Reference in New Issue
Block a user