2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 2025.12.0b1
|
||||
PROJECT_NUMBER = 2025.12.0b2
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
@@ -579,7 +579,7 @@ message LightCommandRequest {
|
||||
bool has_flash_length = 16;
|
||||
uint32 flash_length = 17;
|
||||
bool has_effect = 18;
|
||||
string effect = 19;
|
||||
string effect = 19 [(pointer_to_buffer) = true];
|
||||
uint32 device_id = 28 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
|
||||
|
||||
@@ -533,7 +533,7 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
|
||||
if (msg.has_flash_length)
|
||||
call.set_flash_length(msg.flash_length);
|
||||
if (msg.has_effect)
|
||||
call.set_effect(msg.effect);
|
||||
call.set_effect(reinterpret_cast<const char *>(msg.effect), msg.effect_len);
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
@@ -1669,7 +1669,7 @@ bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryption
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to clear encryption key");
|
||||
}
|
||||
} else if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) {
|
||||
} else if (base64_decode(msg.key, psk.data(), psk.size()) != psk.size()) {
|
||||
ESP_LOGW(TAG, "Invalid encryption key length");
|
||||
} else if (!this->parent_->save_noise_psk(psk, true)) {
|
||||
ESP_LOGW(TAG, "Failed to save encryption key");
|
||||
|
||||
@@ -611,9 +611,12 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
}
|
||||
bool LightCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 19:
|
||||
this->effect = value.as_string();
|
||||
case 19: {
|
||||
// Use raw data directly to avoid allocation
|
||||
this->effect = value.data();
|
||||
this->effect_len = value.size();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -840,7 +840,7 @@ class LightStateResponse final : public StateResponseProtoMessage {
|
||||
class LightCommandRequest final : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 32;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 112;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 122;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "light_command_request"; }
|
||||
#endif
|
||||
@@ -869,7 +869,8 @@ class LightCommandRequest final : public CommandProtoMessage {
|
||||
bool has_flash_length{false};
|
||||
uint32_t flash_length{0};
|
||||
bool has_effect{false};
|
||||
std::string effect{};
|
||||
const uint8_t *effect{nullptr};
|
||||
uint16_t effect_len{0};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
@@ -999,7 +999,9 @@ void LightCommandRequest::dump_to(std::string &out) const {
|
||||
dump_field(out, "has_flash_length", this->has_flash_length);
|
||||
dump_field(out, "flash_length", this->flash_length);
|
||||
dump_field(out, "has_effect", this->has_effect);
|
||||
dump_field(out, "effect", this->effect);
|
||||
out.append(" effect: ");
|
||||
out.append(format_hex_pretty(this->effect, this->effect_len));
|
||||
out.append("\n");
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
|
||||
@@ -36,6 +36,10 @@ void HttpRequestUpdate::setup() {
|
||||
}
|
||||
|
||||
void HttpRequestUpdate::update() {
|
||||
if (!network::is_connected()) {
|
||||
ESP_LOGD(TAG, "Network not connected, skipping update check");
|
||||
return;
|
||||
}
|
||||
#ifdef USE_ESP32
|
||||
xTaskCreate(HttpRequestUpdate::update_task, "update_task", 8192, (void *) this, 1, &this->update_task_handle_);
|
||||
#else
|
||||
|
||||
@@ -504,8 +504,8 @@ color_mode_bitmask_t LightCall::get_suitable_color_modes_mask_() {
|
||||
#undef KEY
|
||||
}
|
||||
|
||||
LightCall &LightCall::set_effect(const std::string &effect) {
|
||||
if (strcasecmp(effect.c_str(), "none") == 0) {
|
||||
LightCall &LightCall::set_effect(const char *effect, size_t len) {
|
||||
if (len == 4 && strncasecmp(effect, "none", 4) == 0) {
|
||||
this->set_effect(0);
|
||||
return *this;
|
||||
}
|
||||
@@ -513,15 +513,16 @@ LightCall &LightCall::set_effect(const std::string &effect) {
|
||||
bool found = false;
|
||||
for (uint32_t i = 0; i < this->parent_->effects_.size(); i++) {
|
||||
LightEffect *e = this->parent_->effects_[i];
|
||||
const char *name = e->get_name();
|
||||
|
||||
if (strcasecmp(effect.c_str(), e->get_name()) == 0) {
|
||||
if (strncasecmp(effect, name, len) == 0 && name[len] == '\0') {
|
||||
this->set_effect(i + 1);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
ESP_LOGW(TAG, "'%s': no such effect '%s'", this->parent_->get_name().c_str(), effect.c_str());
|
||||
ESP_LOGW(TAG, "'%s': no such effect '%.*s'", this->parent_->get_name().c_str(), (int) len, effect);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -129,7 +129,9 @@ class LightCall {
|
||||
/// Set the effect of the light by its name.
|
||||
LightCall &set_effect(optional<std::string> effect);
|
||||
/// Set the effect of the light by its name.
|
||||
LightCall &set_effect(const std::string &effect);
|
||||
LightCall &set_effect(const std::string &effect) { return this->set_effect(effect.data(), effect.size()); }
|
||||
/// Set the effect of the light by its name and length (zero-copy from API).
|
||||
LightCall &set_effect(const char *effect, size_t len);
|
||||
/// Set the effect of the light by its internal index number (only for internal use).
|
||||
LightCall &set_effect(uint32_t effect_number);
|
||||
LightCall &set_effect(optional<uint32_t> effect_number);
|
||||
|
||||
@@ -14,13 +14,36 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#include <coredecls.h> // For esp_schedule()
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace socket {
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
// Flag to signal socket activity - checked by socket_delay() to exit early
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static volatile bool s_socket_woke = false;
|
||||
|
||||
void socket_delay(uint32_t ms) {
|
||||
// Use esp_delay with a callback that checks if socket data arrived.
|
||||
// This allows the delay to exit early when socket_wake() is called by
|
||||
// lwip recv_fn/accept_fn callbacks, reducing socket latency.
|
||||
s_socket_woke = false;
|
||||
esp_delay(ms, []() { return !s_socket_woke; });
|
||||
}
|
||||
|
||||
void socket_wake() {
|
||||
s_socket_woke = true;
|
||||
esp_schedule();
|
||||
}
|
||||
#endif
|
||||
|
||||
static const char *const TAG = "socket.lwip";
|
||||
|
||||
// set to 1 to enable verbose lwip logging
|
||||
#if 0
|
||||
#if 0 // NOLINT(readability-avoid-unconditional-preprocessor-if)
|
||||
#define LWIP_LOG(msg, ...) ESP_LOGVV(TAG, "socket %p: " msg, this, ##__VA_ARGS__)
|
||||
#else
|
||||
#define LWIP_LOG(msg, ...)
|
||||
@@ -323,9 +346,10 @@ class LWIPRawImpl : public Socket {
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
ssize_t err = read(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len);
|
||||
if (err == -1) {
|
||||
if (ret != 0)
|
||||
if (ret != 0) {
|
||||
// if we already read some don't return an error
|
||||
break;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
ret += err;
|
||||
@@ -393,9 +417,10 @@ class LWIPRawImpl : public Socket {
|
||||
ssize_t written = internal_write(buf, len);
|
||||
if (written == -1)
|
||||
return -1;
|
||||
if (written == 0)
|
||||
if (written == 0) {
|
||||
// no need to output if nothing written
|
||||
return 0;
|
||||
}
|
||||
if (nodelay_) {
|
||||
int err = internal_output();
|
||||
if (err == -1)
|
||||
@@ -408,18 +433,20 @@ class LWIPRawImpl : public Socket {
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
ssize_t err = internal_write(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len);
|
||||
if (err == -1) {
|
||||
if (written != 0)
|
||||
if (written != 0) {
|
||||
// if we already read some don't return an error
|
||||
break;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
written += err;
|
||||
if ((size_t) err != iov[i].iov_len)
|
||||
break;
|
||||
}
|
||||
if (written == 0)
|
||||
if (written == 0) {
|
||||
// no need to output if nothing written
|
||||
return 0;
|
||||
}
|
||||
if (nodelay_) {
|
||||
int err = internal_output();
|
||||
if (err == -1)
|
||||
@@ -473,6 +500,10 @@ class LWIPRawImpl : public Socket {
|
||||
} else {
|
||||
pbuf_cat(rx_buf_, pb);
|
||||
}
|
||||
#ifdef USE_ESP8266
|
||||
// Wake the main loop immediately so it can process the received data.
|
||||
socket_wake();
|
||||
#endif
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
@@ -612,7 +643,7 @@ class LWIPRawListenImpl : public LWIPRawImpl {
|
||||
}
|
||||
|
||||
private:
|
||||
err_t accept_fn(struct tcp_pcb *newpcb, err_t err) {
|
||||
err_t accept_fn_(struct tcp_pcb *newpcb, err_t err) {
|
||||
LWIP_LOG("accept(newpcb=%p err=%d)", newpcb, err);
|
||||
if (err != ERR_OK || newpcb == nullptr) {
|
||||
// "An error code if there has been an error accepting. Only return ERR_ABRT if you have
|
||||
@@ -633,12 +664,16 @@ class LWIPRawListenImpl : public LWIPRawImpl {
|
||||
sock->init();
|
||||
accepted_sockets_[accepted_socket_count_++] = std::move(sock);
|
||||
LWIP_LOG("Accepted connection, queue size: %d", accepted_socket_count_);
|
||||
#ifdef USE_ESP8266
|
||||
// Wake the main loop immediately so it can accept the new connection.
|
||||
socket_wake();
|
||||
#endif
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
static err_t s_accept_fn(void *arg, struct tcp_pcb *newpcb, err_t err) {
|
||||
LWIPRawListenImpl *arg_this = reinterpret_cast<LWIPRawListenImpl *>(arg);
|
||||
return arg_this->accept_fn(newpcb, err);
|
||||
return arg_this->accept_fn_(newpcb, err);
|
||||
}
|
||||
|
||||
// Accept queue - holds incoming connections briefly until the event loop calls accept()
|
||||
|
||||
@@ -82,6 +82,15 @@ socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const std::stri
|
||||
/// Set a sockaddr to the any address and specified port for the IP version used by socket_ip().
|
||||
socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port);
|
||||
|
||||
#if defined(USE_ESP8266) && defined(USE_SOCKET_IMPL_LWIP_TCP)
|
||||
/// Delay that can be woken early by socket activity.
|
||||
/// On ESP8266, lwip callbacks set a flag and call esp_schedule() to wake the delay.
|
||||
void socket_delay(uint32_t ms);
|
||||
|
||||
/// Called by lwip callbacks to signal socket activity and wake delay.
|
||||
void socket_wake();
|
||||
#endif
|
||||
|
||||
} // namespace socket
|
||||
} // namespace esphome
|
||||
#endif
|
||||
|
||||
@@ -4,7 +4,7 @@ from enum import Enum
|
||||
|
||||
from esphome.enum import StrEnum
|
||||
|
||||
__version__ = "2025.12.0b1"
|
||||
__version__ = "2025.12.0b2"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
#include "esphome/components/status_led/status_led.h"
|
||||
#endif
|
||||
|
||||
#if defined(USE_ESP8266) && defined(USE_SOCKET_IMPL_LWIP_TCP)
|
||||
#include "esphome/components/socket/socket.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
#include <cerrno>
|
||||
|
||||
@@ -627,6 +631,9 @@ void Application::yield_with_select_(uint32_t delay_ms) {
|
||||
// No sockets registered, use regular delay
|
||||
delay(delay_ms);
|
||||
}
|
||||
#elif defined(USE_ESP8266) && defined(USE_SOCKET_IMPL_LWIP_TCP)
|
||||
// No select support but can wake on socket activity via esp_schedule()
|
||||
socket::socket_delay(delay_ms);
|
||||
#else
|
||||
// No select support, use regular delay
|
||||
delay(delay_ms);
|
||||
|
||||
@@ -480,22 +480,13 @@ std::string base64_encode(const uint8_t *buf, size_t buf_len) {
|
||||
}
|
||||
|
||||
size_t base64_decode(const std::string &encoded_string, uint8_t *buf, size_t buf_len) {
|
||||
std::vector<uint8_t> decoded = base64_decode(encoded_string);
|
||||
if (decoded.size() > buf_len) {
|
||||
ESP_LOGW(TAG, "Base64 decode: buffer too small, truncating");
|
||||
decoded.resize(buf_len);
|
||||
}
|
||||
memcpy(buf, decoded.data(), decoded.size());
|
||||
return decoded.size();
|
||||
}
|
||||
|
||||
std::vector<uint8_t> base64_decode(const std::string &encoded_string) {
|
||||
int in_len = encoded_string.size();
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
int in = 0;
|
||||
size_t out = 0;
|
||||
uint8_t char_array_4[4], char_array_3[3];
|
||||
std::vector<uint8_t> ret;
|
||||
bool truncated = false;
|
||||
|
||||
// SAFETY: The loop condition checks is_base64() before processing each character.
|
||||
// This ensures base64_find_char() is only called on valid base64 characters,
|
||||
@@ -511,8 +502,13 @@ std::vector<uint8_t> base64_decode(const std::string &encoded_string) {
|
||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
for (i = 0; (i < 3); i++)
|
||||
ret.push_back(char_array_3[i]);
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (out < buf_len) {
|
||||
buf[out++] = char_array_3[i];
|
||||
} else {
|
||||
truncated = true;
|
||||
}
|
||||
}
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
@@ -528,10 +524,28 @@ std::vector<uint8_t> base64_decode(const std::string &encoded_string) {
|
||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
for (j = 0; (j < i - 1); j++)
|
||||
ret.push_back(char_array_3[j]);
|
||||
for (j = 0; j < i - 1; j++) {
|
||||
if (out < buf_len) {
|
||||
buf[out++] = char_array_3[j];
|
||||
} else {
|
||||
truncated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (truncated) {
|
||||
ESP_LOGW(TAG, "Base64 decode: buffer too small, truncating");
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> base64_decode(const std::string &encoded_string) {
|
||||
// Calculate maximum decoded size: every 4 base64 chars = 3 bytes
|
||||
size_t max_len = ((encoded_string.size() + 3) / 4) * 3;
|
||||
std::vector<uint8_t> ret(max_len);
|
||||
size_t actual_len = base64_decode(encoded_string, ret.data(), max_len);
|
||||
ret.resize(actual_len);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user