mirror of
https://github.com/esphome/esphome.git
synced 2025-10-13 07:13:47 +01:00
Merge branch 'dev' into jesserockz-2025-457
This commit is contained in:
@@ -1 +1 @@
|
|||||||
ab49c22900dd39c004623e450a1076b111d6741f31967a637ab6e0e3dd2e753e
|
049d60eed541730efaa4c0dc5d337b4287bf29b6daa350b5dfc1f23915f1c52f
|
||||||
|
@@ -18,6 +18,17 @@ namespace esphome::api {
|
|||||||
// uncomment to log raw packets
|
// uncomment to log raw packets
|
||||||
//#define HELPER_LOG_PACKETS
|
//#define HELPER_LOG_PACKETS
|
||||||
|
|
||||||
|
// Maximum message size limits to prevent OOM on constrained devices
|
||||||
|
// Handshake messages are limited to a small size for security
|
||||||
|
static constexpr uint16_t MAX_HANDSHAKE_SIZE = 128;
|
||||||
|
|
||||||
|
// Data message limits vary by platform based on available memory
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
static constexpr uint16_t MAX_MESSAGE_SIZE = 8192; // 8 KiB for ESP8266
|
||||||
|
#else
|
||||||
|
static constexpr uint16_t MAX_MESSAGE_SIZE = 32768; // 32 KiB for ESP32 and other platforms
|
||||||
|
#endif
|
||||||
|
|
||||||
// Forward declaration
|
// Forward declaration
|
||||||
struct ClientInfo;
|
struct ClientInfo;
|
||||||
|
|
||||||
|
@@ -132,26 +132,16 @@ APIError APINoiseFrameHelper::loop() {
|
|||||||
return APIFrameHelper::loop();
|
return APIFrameHelper::loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
|
/** Read a packet into the rx_buf_.
|
||||||
*
|
*
|
||||||
* @param frame: The struct to hold the frame information in.
|
* @return APIError::OK if a full packet is in rx_buf_
|
||||||
* msg_start: points to the start of the payload - this pointer is only valid until the next
|
|
||||||
* try_receive_raw_ call
|
|
||||||
*
|
|
||||||
* @return 0 if a full packet is in rx_buf_
|
|
||||||
* @return -1 if error, check errno.
|
|
||||||
*
|
*
|
||||||
* errno EWOULDBLOCK: Packet could not be read without blocking. Try again later.
|
* errno EWOULDBLOCK: Packet could not be read without blocking. Try again later.
|
||||||
* errno ENOMEM: Not enough memory for reading packet.
|
* errno ENOMEM: Not enough memory for reading packet.
|
||||||
* errno API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
|
* errno API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
|
||||||
* errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase.
|
* errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase.
|
||||||
*/
|
*/
|
||||||
APIError APINoiseFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
|
APIError APINoiseFrameHelper::try_read_frame_() {
|
||||||
if (frame == nullptr) {
|
|
||||||
HELPER_LOG("Bad argument for try_read_frame_");
|
|
||||||
return APIError::BAD_ARG;
|
|
||||||
}
|
|
||||||
|
|
||||||
// read header
|
// read header
|
||||||
if (rx_header_buf_len_ < 3) {
|
if (rx_header_buf_len_ < 3) {
|
||||||
// no header information yet
|
// no header information yet
|
||||||
@@ -178,16 +168,17 @@ APIError APINoiseFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
|
|||||||
// read body
|
// read body
|
||||||
uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2];
|
uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2];
|
||||||
|
|
||||||
if (state_ != State::DATA && msg_size > 128) {
|
// Check against size limits to prevent OOM: MAX_HANDSHAKE_SIZE for handshake, MAX_MESSAGE_SIZE for data
|
||||||
// for handshake message only permit up to 128 bytes
|
uint16_t limit = (state_ == State::DATA) ? MAX_MESSAGE_SIZE : MAX_HANDSHAKE_SIZE;
|
||||||
|
if (msg_size > limit) {
|
||||||
state_ = State::FAILED;
|
state_ = State::FAILED;
|
||||||
HELPER_LOG("Bad packet len for handshake: %d", msg_size);
|
HELPER_LOG("Bad packet: message size %u exceeds maximum %u", msg_size, limit);
|
||||||
return APIError::BAD_HANDSHAKE_PACKET_LEN;
|
return (state_ == State::DATA) ? APIError::BAD_DATA_PACKET : APIError::BAD_HANDSHAKE_PACKET_LEN;
|
||||||
}
|
}
|
||||||
|
|
||||||
// reserve space for body
|
// Reserve space for body
|
||||||
if (rx_buf_.size() != msg_size) {
|
if (this->rx_buf_.size() != msg_size) {
|
||||||
rx_buf_.resize(msg_size);
|
this->rx_buf_.resize(msg_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rx_buf_len_ < msg_size) {
|
if (rx_buf_len_ < msg_size) {
|
||||||
@@ -205,12 +196,12 @@ APIError APINoiseFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_PACKET_RECEIVED(rx_buf_);
|
LOG_PACKET_RECEIVED(this->rx_buf_);
|
||||||
*frame = std::move(rx_buf_);
|
|
||||||
// consume msg
|
// Clear state for next frame (rx_buf_ still contains data for caller)
|
||||||
rx_buf_ = {};
|
this->rx_buf_len_ = 0;
|
||||||
rx_buf_len_ = 0;
|
this->rx_header_buf_len_ = 0;
|
||||||
rx_header_buf_len_ = 0;
|
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,18 +223,17 @@ APIError APINoiseFrameHelper::state_action_() {
|
|||||||
}
|
}
|
||||||
if (state_ == State::CLIENT_HELLO) {
|
if (state_ == State::CLIENT_HELLO) {
|
||||||
// waiting for client hello
|
// waiting for client hello
|
||||||
std::vector<uint8_t> frame;
|
aerr = this->try_read_frame_();
|
||||||
aerr = try_read_frame_(&frame);
|
|
||||||
if (aerr != APIError::OK) {
|
if (aerr != APIError::OK) {
|
||||||
return handle_handshake_frame_error_(aerr);
|
return handle_handshake_frame_error_(aerr);
|
||||||
}
|
}
|
||||||
// ignore contents, may be used in future for flags
|
// ignore contents, may be used in future for flags
|
||||||
// Resize for: existing prologue + 2 size bytes + frame data
|
// Resize for: existing prologue + 2 size bytes + frame data
|
||||||
size_t old_size = prologue_.size();
|
size_t old_size = this->prologue_.size();
|
||||||
prologue_.resize(old_size + 2 + frame.size());
|
this->prologue_.resize(old_size + 2 + this->rx_buf_.size());
|
||||||
prologue_[old_size] = (uint8_t) (frame.size() >> 8);
|
this->prologue_[old_size] = (uint8_t) (this->rx_buf_.size() >> 8);
|
||||||
prologue_[old_size + 1] = (uint8_t) frame.size();
|
this->prologue_[old_size + 1] = (uint8_t) this->rx_buf_.size();
|
||||||
std::memcpy(prologue_.data() + old_size + 2, frame.data(), frame.size());
|
std::memcpy(this->prologue_.data() + old_size + 2, this->rx_buf_.data(), this->rx_buf_.size());
|
||||||
|
|
||||||
state_ = State::SERVER_HELLO;
|
state_ = State::SERVER_HELLO;
|
||||||
}
|
}
|
||||||
@@ -285,24 +275,23 @@ APIError APINoiseFrameHelper::state_action_() {
|
|||||||
int action = noise_handshakestate_get_action(handshake_);
|
int action = noise_handshakestate_get_action(handshake_);
|
||||||
if (action == NOISE_ACTION_READ_MESSAGE) {
|
if (action == NOISE_ACTION_READ_MESSAGE) {
|
||||||
// waiting for handshake msg
|
// waiting for handshake msg
|
||||||
std::vector<uint8_t> frame;
|
aerr = this->try_read_frame_();
|
||||||
aerr = try_read_frame_(&frame);
|
|
||||||
if (aerr != APIError::OK) {
|
if (aerr != APIError::OK) {
|
||||||
return handle_handshake_frame_error_(aerr);
|
return handle_handshake_frame_error_(aerr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frame.empty()) {
|
if (this->rx_buf_.empty()) {
|
||||||
send_explicit_handshake_reject_(LOG_STR("Empty handshake message"));
|
send_explicit_handshake_reject_(LOG_STR("Empty handshake message"));
|
||||||
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
||||||
} else if (frame[0] != 0x00) {
|
} else if (this->rx_buf_[0] != 0x00) {
|
||||||
HELPER_LOG("Bad handshake error byte: %u", frame[0]);
|
HELPER_LOG("Bad handshake error byte: %u", this->rx_buf_[0]);
|
||||||
send_explicit_handshake_reject_(LOG_STR("Bad handshake error byte"));
|
send_explicit_handshake_reject_(LOG_STR("Bad handshake error byte"));
|
||||||
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
||||||
}
|
}
|
||||||
|
|
||||||
NoiseBuffer mbuf;
|
NoiseBuffer mbuf;
|
||||||
noise_buffer_init(mbuf);
|
noise_buffer_init(mbuf);
|
||||||
noise_buffer_set_input(mbuf, frame.data() + 1, frame.size() - 1);
|
noise_buffer_set_input(mbuf, this->rx_buf_.data() + 1, this->rx_buf_.size() - 1);
|
||||||
err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
|
err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
// Special handling for MAC failure
|
// Special handling for MAC failure
|
||||||
@@ -379,35 +368,33 @@ void APINoiseFrameHelper::send_explicit_handshake_reject_(const LogString *reaso
|
|||||||
state_ = orig_state;
|
state_ = orig_state;
|
||||||
}
|
}
|
||||||
APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||||
int err;
|
APIError aerr = this->state_action_();
|
||||||
APIError aerr;
|
|
||||||
aerr = state_action_();
|
|
||||||
if (aerr != APIError::OK) {
|
if (aerr != APIError::OK) {
|
||||||
return aerr;
|
return aerr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state_ != State::DATA) {
|
if (this->state_ != State::DATA) {
|
||||||
return APIError::WOULD_BLOCK;
|
return APIError::WOULD_BLOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> frame;
|
aerr = this->try_read_frame_();
|
||||||
aerr = try_read_frame_(&frame);
|
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
return aerr;
|
return aerr;
|
||||||
|
|
||||||
NoiseBuffer mbuf;
|
NoiseBuffer mbuf;
|
||||||
noise_buffer_init(mbuf);
|
noise_buffer_init(mbuf);
|
||||||
noise_buffer_set_inout(mbuf, frame.data(), frame.size(), frame.size());
|
noise_buffer_set_inout(mbuf, this->rx_buf_.data(), this->rx_buf_.size(), this->rx_buf_.size());
|
||||||
err = noise_cipherstate_decrypt(recv_cipher_, &mbuf);
|
int err = noise_cipherstate_decrypt(this->recv_cipher_, &mbuf);
|
||||||
APIError decrypt_err =
|
APIError decrypt_err =
|
||||||
handle_noise_error_(err, LOG_STR("noise_cipherstate_decrypt"), APIError::CIPHERSTATE_DECRYPT_FAILED);
|
handle_noise_error_(err, LOG_STR("noise_cipherstate_decrypt"), APIError::CIPHERSTATE_DECRYPT_FAILED);
|
||||||
if (decrypt_err != APIError::OK)
|
if (decrypt_err != APIError::OK) {
|
||||||
return decrypt_err;
|
return decrypt_err;
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t msg_size = mbuf.size;
|
uint16_t msg_size = mbuf.size;
|
||||||
uint8_t *msg_data = frame.data();
|
uint8_t *msg_data = this->rx_buf_.data();
|
||||||
if (msg_size < 4) {
|
if (msg_size < 4) {
|
||||||
state_ = State::FAILED;
|
this->state_ = State::FAILED;
|
||||||
HELPER_LOG("Bad data packet: size %d too short", msg_size);
|
HELPER_LOG("Bad data packet: size %d too short", msg_size);
|
||||||
return APIError::BAD_DATA_PACKET;
|
return APIError::BAD_DATA_PACKET;
|
||||||
}
|
}
|
||||||
@@ -415,12 +402,12 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
|||||||
uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1];
|
uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1];
|
||||||
uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
|
uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
|
||||||
if (data_len > msg_size - 4) {
|
if (data_len > msg_size - 4) {
|
||||||
state_ = State::FAILED;
|
this->state_ = State::FAILED;
|
||||||
HELPER_LOG("Bad data packet: data_len %u greater than msg_size %u", data_len, msg_size);
|
HELPER_LOG("Bad data packet: data_len %u greater than msg_size %u", data_len, msg_size);
|
||||||
return APIError::BAD_DATA_PACKET;
|
return APIError::BAD_DATA_PACKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer->container = std::move(frame);
|
buffer->container = std::move(this->rx_buf_);
|
||||||
buffer->data_offset = 4;
|
buffer->data_offset = 4;
|
||||||
buffer->data_len = data_len;
|
buffer->data_len = data_len;
|
||||||
buffer->type = type;
|
buffer->type = type;
|
||||||
|
@@ -28,7 +28,7 @@ class APINoiseFrameHelper final : public APIFrameHelper {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
APIError state_action_();
|
APIError state_action_();
|
||||||
APIError try_read_frame_(std::vector<uint8_t> *frame);
|
APIError try_read_frame_();
|
||||||
APIError write_frame_(const uint8_t *data, uint16_t len);
|
APIError write_frame_(const uint8_t *data, uint16_t len);
|
||||||
APIError init_handshake_();
|
APIError init_handshake_();
|
||||||
APIError check_handshake_finished_();
|
APIError check_handshake_finished_();
|
||||||
|
@@ -47,21 +47,13 @@ APIError APIPlaintextFrameHelper::loop() {
|
|||||||
return APIFrameHelper::loop();
|
return APIFrameHelper::loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
|
/** Read a packet into the rx_buf_.
|
||||||
*
|
|
||||||
* @param frame: The struct to hold the frame information in.
|
|
||||||
* msg: store the parsed frame in that struct
|
|
||||||
*
|
*
|
||||||
* @return See APIError
|
* @return See APIError
|
||||||
*
|
*
|
||||||
* error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
|
* error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
|
||||||
*/
|
*/
|
||||||
APIError APIPlaintextFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
|
APIError APIPlaintextFrameHelper::try_read_frame_() {
|
||||||
if (frame == nullptr) {
|
|
||||||
HELPER_LOG("Bad argument for try_read_frame_");
|
|
||||||
return APIError::BAD_ARG;
|
|
||||||
}
|
|
||||||
|
|
||||||
// read header
|
// read header
|
||||||
while (!rx_header_parsed_) {
|
while (!rx_header_parsed_) {
|
||||||
// Now that we know when the socket is ready, we can read up to 3 bytes
|
// Now that we know when the socket is ready, we can read up to 3 bytes
|
||||||
@@ -123,10 +115,10 @@ APIError APIPlaintextFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg_size_varint->as_uint32() > std::numeric_limits<uint16_t>::max()) {
|
if (msg_size_varint->as_uint32() > MAX_MESSAGE_SIZE) {
|
||||||
state_ = State::FAILED;
|
state_ = State::FAILED;
|
||||||
HELPER_LOG("Bad packet: message size %" PRIu32 " exceeds maximum %u", msg_size_varint->as_uint32(),
|
HELPER_LOG("Bad packet: message size %" PRIu32 " exceeds maximum %u", msg_size_varint->as_uint32(),
|
||||||
std::numeric_limits<uint16_t>::max());
|
MAX_MESSAGE_SIZE);
|
||||||
return APIError::BAD_DATA_PACKET;
|
return APIError::BAD_DATA_PACKET;
|
||||||
}
|
}
|
||||||
rx_header_parsed_len_ = msg_size_varint->as_uint16();
|
rx_header_parsed_len_ = msg_size_varint->as_uint16();
|
||||||
@@ -150,9 +142,9 @@ APIError APIPlaintextFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
|
|||||||
}
|
}
|
||||||
// header reading done
|
// header reading done
|
||||||
|
|
||||||
// reserve space for body
|
// Reserve space for body
|
||||||
if (rx_buf_.size() != rx_header_parsed_len_) {
|
if (this->rx_buf_.size() != this->rx_header_parsed_len_) {
|
||||||
rx_buf_.resize(rx_header_parsed_len_);
|
this->rx_buf_.resize(this->rx_header_parsed_len_);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rx_buf_len_ < rx_header_parsed_len_) {
|
if (rx_buf_len_ < rx_header_parsed_len_) {
|
||||||
@@ -170,24 +162,22 @@ APIError APIPlaintextFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_PACKET_RECEIVED(rx_buf_);
|
LOG_PACKET_RECEIVED(this->rx_buf_);
|
||||||
*frame = std::move(rx_buf_);
|
|
||||||
// consume msg
|
// Clear state for next frame (rx_buf_ still contains data for caller)
|
||||||
rx_buf_ = {};
|
this->rx_buf_len_ = 0;
|
||||||
rx_buf_len_ = 0;
|
this->rx_header_buf_pos_ = 0;
|
||||||
rx_header_buf_pos_ = 0;
|
this->rx_header_parsed_ = false;
|
||||||
rx_header_parsed_ = false;
|
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
|
||||||
APIError aerr;
|
|
||||||
|
|
||||||
if (state_ != State::DATA) {
|
APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||||
|
if (this->state_ != State::DATA) {
|
||||||
return APIError::WOULD_BLOCK;
|
return APIError::WOULD_BLOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> frame;
|
APIError aerr = this->try_read_frame_();
|
||||||
aerr = try_read_frame_(&frame);
|
|
||||||
if (aerr != APIError::OK) {
|
if (aerr != APIError::OK) {
|
||||||
if (aerr == APIError::BAD_INDICATOR) {
|
if (aerr == APIError::BAD_INDICATOR) {
|
||||||
// Make sure to tell the remote that we don't
|
// Make sure to tell the remote that we don't
|
||||||
@@ -220,10 +210,10 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
|||||||
return aerr;
|
return aerr;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer->container = std::move(frame);
|
buffer->container = std::move(this->rx_buf_);
|
||||||
buffer->data_offset = 0;
|
buffer->data_offset = 0;
|
||||||
buffer->data_len = rx_header_parsed_len_;
|
buffer->data_len = this->rx_header_parsed_len_;
|
||||||
buffer->type = rx_header_parsed_type_;
|
buffer->type = this->rx_header_parsed_type_;
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
|
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
|
||||||
|
@@ -24,7 +24,7 @@ class APIPlaintextFrameHelper final : public APIFrameHelper {
|
|||||||
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
APIError try_read_frame_(std::vector<uint8_t> *frame);
|
APIError try_read_frame_();
|
||||||
|
|
||||||
// Group 2-byte aligned types
|
// Group 2-byte aligned types
|
||||||
uint16_t rx_header_parsed_type_ = 0;
|
uint16_t rx_header_parsed_type_ = 0;
|
||||||
|
@@ -165,4 +165,4 @@ def final_validate_audio_schema(
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_library("esphome/esp-audio-libs", "1.1.4")
|
cg.add_library("esphome/esp-audio-libs", "2.0.1")
|
||||||
|
@@ -229,18 +229,18 @@ FileDecoderState AudioDecoder::decode_flac_() {
|
|||||||
auto result = this->flac_decoder_->read_header(this->input_transfer_buffer_->get_buffer_start(),
|
auto result = this->flac_decoder_->read_header(this->input_transfer_buffer_->get_buffer_start(),
|
||||||
this->input_transfer_buffer_->available());
|
this->input_transfer_buffer_->available());
|
||||||
|
|
||||||
if (result == esp_audio_libs::flac::FLAC_DECODER_HEADER_OUT_OF_DATA) {
|
if (result > esp_audio_libs::flac::FLAC_DECODER_HEADER_OUT_OF_DATA) {
|
||||||
return FileDecoderState::POTENTIALLY_FAILED;
|
// Serrious error reading FLAC header, there is no recovery
|
||||||
}
|
|
||||||
|
|
||||||
if (result != esp_audio_libs::flac::FLAC_DECODER_SUCCESS) {
|
|
||||||
// Couldn't read FLAC header
|
|
||||||
return FileDecoderState::FAILED;
|
return FileDecoderState::FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t bytes_consumed = this->flac_decoder_->get_bytes_index();
|
size_t bytes_consumed = this->flac_decoder_->get_bytes_index();
|
||||||
this->input_transfer_buffer_->decrease_buffer_length(bytes_consumed);
|
this->input_transfer_buffer_->decrease_buffer_length(bytes_consumed);
|
||||||
|
|
||||||
|
if (result == esp_audio_libs::flac::FLAC_DECODER_HEADER_OUT_OF_DATA) {
|
||||||
|
return FileDecoderState::MORE_TO_PROCESS;
|
||||||
|
}
|
||||||
|
|
||||||
// Reallocate the output transfer buffer to the smallest necessary size
|
// Reallocate the output transfer buffer to the smallest necessary size
|
||||||
this->free_buffer_required_ = flac_decoder_->get_output_buffer_size_bytes();
|
this->free_buffer_required_ = flac_decoder_->get_output_buffer_size_bytes();
|
||||||
if (!this->output_transfer_buffer_->reallocate(this->free_buffer_required_)) {
|
if (!this->output_transfer_buffer_->reallocate(this->free_buffer_required_)) {
|
||||||
@@ -256,9 +256,9 @@ FileDecoderState AudioDecoder::decode_flac_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t output_samples = 0;
|
uint32_t output_samples = 0;
|
||||||
auto result = this->flac_decoder_->decode_frame(
|
auto result = this->flac_decoder_->decode_frame(this->input_transfer_buffer_->get_buffer_start(),
|
||||||
this->input_transfer_buffer_->get_buffer_start(), this->input_transfer_buffer_->available(),
|
this->input_transfer_buffer_->available(),
|
||||||
reinterpret_cast<int16_t *>(this->output_transfer_buffer_->get_buffer_end()), &output_samples);
|
this->output_transfer_buffer_->get_buffer_end(), &output_samples);
|
||||||
|
|
||||||
if (result == esp_audio_libs::flac::FLAC_DECODER_ERROR_OUT_OF_DATA) {
|
if (result == esp_audio_libs::flac::FLAC_DECODER_ERROR_OUT_OF_DATA) {
|
||||||
// Not an issue, just needs more data that we'll get next time.
|
// Not an issue, just needs more data that we'll get next time.
|
||||||
|
@@ -42,32 +42,18 @@ ESPBTUUID ESPBTUUID::from_raw_reversed(const uint8_t *data) {
|
|||||||
ESPBTUUID ESPBTUUID::from_raw(const std::string &data) {
|
ESPBTUUID ESPBTUUID::from_raw(const std::string &data) {
|
||||||
ESPBTUUID ret;
|
ESPBTUUID ret;
|
||||||
if (data.length() == 4) {
|
if (data.length() == 4) {
|
||||||
|
// 16-bit UUID as 4-character hex string
|
||||||
|
auto parsed = parse_hex<uint16_t>(data);
|
||||||
|
if (parsed.has_value()) {
|
||||||
ret.uuid_.len = ESP_UUID_LEN_16;
|
ret.uuid_.len = ESP_UUID_LEN_16;
|
||||||
ret.uuid_.uuid.uuid16 = 0;
|
ret.uuid_.uuid.uuid16 = parsed.value();
|
||||||
for (uint i = 0; i < data.length(); i += 2) {
|
|
||||||
uint8_t msb = data.c_str()[i];
|
|
||||||
uint8_t lsb = data.c_str()[i + 1];
|
|
||||||
uint8_t lsb_shift = i <= 2 ? (2 - i) * 4 : 0;
|
|
||||||
|
|
||||||
if (msb > '9')
|
|
||||||
msb -= 7;
|
|
||||||
if (lsb > '9')
|
|
||||||
lsb -= 7;
|
|
||||||
ret.uuid_.uuid.uuid16 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << lsb_shift;
|
|
||||||
}
|
}
|
||||||
} else if (data.length() == 8) {
|
} else if (data.length() == 8) {
|
||||||
|
// 32-bit UUID as 8-character hex string
|
||||||
|
auto parsed = parse_hex<uint32_t>(data);
|
||||||
|
if (parsed.has_value()) {
|
||||||
ret.uuid_.len = ESP_UUID_LEN_32;
|
ret.uuid_.len = ESP_UUID_LEN_32;
|
||||||
ret.uuid_.uuid.uuid32 = 0;
|
ret.uuid_.uuid.uuid32 = parsed.value();
|
||||||
for (uint i = 0; i < data.length(); i += 2) {
|
|
||||||
uint8_t msb = data.c_str()[i];
|
|
||||||
uint8_t lsb = data.c_str()[i + 1];
|
|
||||||
uint8_t lsb_shift = i <= 6 ? (6 - i) * 4 : 0;
|
|
||||||
|
|
||||||
if (msb > '9')
|
|
||||||
msb -= 7;
|
|
||||||
if (lsb > '9')
|
|
||||||
lsb -= 7;
|
|
||||||
ret.uuid_.uuid.uuid32 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << lsb_shift;
|
|
||||||
}
|
}
|
||||||
} else if (data.length() == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be
|
} else if (data.length() == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be
|
||||||
// investigated (lack of time)
|
// investigated (lack of time)
|
||||||
@@ -145,29 +131,17 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const {
|
|||||||
if (this->uuid_.len == uuid.uuid_.len) {
|
if (this->uuid_.len == uuid.uuid_.len) {
|
||||||
switch (this->uuid_.len) {
|
switch (this->uuid_.len) {
|
||||||
case ESP_UUID_LEN_16:
|
case ESP_UUID_LEN_16:
|
||||||
if (uuid.uuid_.uuid.uuid16 == this->uuid_.uuid.uuid16) {
|
return this->uuid_.uuid.uuid16 == uuid.uuid_.uuid.uuid16;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ESP_UUID_LEN_32:
|
case ESP_UUID_LEN_32:
|
||||||
if (uuid.uuid_.uuid.uuid32 == this->uuid_.uuid.uuid32) {
|
return this->uuid_.uuid.uuid32 == uuid.uuid_.uuid.uuid32;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ESP_UUID_LEN_128:
|
case ESP_UUID_LEN_128:
|
||||||
for (uint8_t i = 0; i < ESP_UUID_LEN_128; i++) {
|
return memcmp(this->uuid_.uuid.uuid128, uuid.uuid_.uuid.uuid128, ESP_UUID_LEN_128) == 0;
|
||||||
if (uuid.uuid_.uuid.uuid128[i] != this->uuid_.uuid.uuid128[i]) {
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return this->as_128bit() == uuid.as_128bit();
|
return this->as_128bit() == uuid.as_128bit();
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; }
|
esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; }
|
||||||
std::string ESPBTUUID::to_string() const {
|
std::string ESPBTUUID::to_string() const {
|
||||||
char buf[40]; // Enough for 128-bit UUID with dashes
|
char buf[40]; // Enough for 128-bit UUID with dashes
|
||||||
|
@@ -10,11 +10,15 @@ namespace light {
|
|||||||
static const char *const TAG = "light";
|
static const char *const TAG = "light";
|
||||||
|
|
||||||
// Helper functions to reduce code size for logging
|
// Helper functions to reduce code size for logging
|
||||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_WARN
|
static void clamp_and_log_if_invalid(const char *name, float &value, const LogString *param_name, float min = 0.0f,
|
||||||
static void log_validation_warning(const char *name, const LogString *param_name, float val, float min, float max) {
|
float max = 1.0f) {
|
||||||
ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, LOG_STR_ARG(param_name), val, min, max);
|
if (value < min || value > max) {
|
||||||
|
ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, LOG_STR_ARG(param_name), value, min, max);
|
||||||
|
value = clamp(value, min, max);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_WARN
|
||||||
static void log_feature_not_supported(const char *name, const LogString *feature) {
|
static void log_feature_not_supported(const char *name, const LogString *feature) {
|
||||||
ESP_LOGW(TAG, "'%s': %s not supported", name, LOG_STR_ARG(feature));
|
ESP_LOGW(TAG, "'%s': %s not supported", name, LOG_STR_ARG(feature));
|
||||||
}
|
}
|
||||||
@@ -27,7 +31,6 @@ static void log_invalid_parameter(const char *name, const LogString *message) {
|
|||||||
ESP_LOGW(TAG, "'%s': %s", name, LOG_STR_ARG(message));
|
ESP_LOGW(TAG, "'%s': %s", name, LOG_STR_ARG(message));
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
#define log_validation_warning(name, param_name, val, min, max)
|
|
||||||
#define log_feature_not_supported(name, feature)
|
#define log_feature_not_supported(name, feature)
|
||||||
#define log_color_mode_not_supported(name, feature)
|
#define log_color_mode_not_supported(name, feature)
|
||||||
#define log_invalid_parameter(name, message)
|
#define log_invalid_parameter(name, message)
|
||||||
@@ -44,7 +47,7 @@ static void log_invalid_parameter(const char *name, const LogString *message) {
|
|||||||
} \
|
} \
|
||||||
LightCall &LightCall::set_##name(type name) { \
|
LightCall &LightCall::set_##name(type name) { \
|
||||||
this->name##_ = name; \
|
this->name##_ = name; \
|
||||||
this->set_flag_(flag, true); \
|
this->set_flag_(flag); \
|
||||||
return *this; \
|
return *this; \
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,6 +184,16 @@ void LightCall::perform() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LightCall::log_and_clear_unsupported_(FieldFlags flag, const LogString *feature, bool use_color_mode_log) {
|
||||||
|
auto *name = this->parent_->get_name().c_str();
|
||||||
|
if (use_color_mode_log) {
|
||||||
|
log_color_mode_not_supported(name, feature);
|
||||||
|
} else {
|
||||||
|
log_feature_not_supported(name, feature);
|
||||||
|
}
|
||||||
|
this->clear_flag_(flag);
|
||||||
|
}
|
||||||
|
|
||||||
LightColorValues LightCall::validate_() {
|
LightColorValues LightCall::validate_() {
|
||||||
auto *name = this->parent_->get_name().c_str();
|
auto *name = this->parent_->get_name().c_str();
|
||||||
auto traits = this->parent_->get_traits();
|
auto traits = this->parent_->get_traits();
|
||||||
@@ -188,141 +201,108 @@ LightColorValues LightCall::validate_() {
|
|||||||
// Color mode check
|
// Color mode check
|
||||||
if (this->has_color_mode() && !traits.supports_color_mode(this->color_mode_)) {
|
if (this->has_color_mode() && !traits.supports_color_mode(this->color_mode_)) {
|
||||||
ESP_LOGW(TAG, "'%s' does not support color mode %s", name, LOG_STR_ARG(color_mode_to_human(this->color_mode_)));
|
ESP_LOGW(TAG, "'%s' does not support color mode %s", name, LOG_STR_ARG(color_mode_to_human(this->color_mode_)));
|
||||||
this->set_flag_(FLAG_HAS_COLOR_MODE, false);
|
this->clear_flag_(FLAG_HAS_COLOR_MODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure there is always a color mode set
|
// Ensure there is always a color mode set
|
||||||
if (!this->has_color_mode()) {
|
if (!this->has_color_mode()) {
|
||||||
this->color_mode_ = this->compute_color_mode_();
|
this->color_mode_ = this->compute_color_mode_();
|
||||||
this->set_flag_(FLAG_HAS_COLOR_MODE, true);
|
this->set_flag_(FLAG_HAS_COLOR_MODE);
|
||||||
}
|
}
|
||||||
auto color_mode = this->color_mode_;
|
auto color_mode = this->color_mode_;
|
||||||
|
|
||||||
// Transform calls that use non-native parameters for the current mode.
|
// Transform calls that use non-native parameters for the current mode.
|
||||||
this->transform_parameters_();
|
this->transform_parameters_();
|
||||||
|
|
||||||
// Brightness exists check
|
// Business logic adjustments before validation
|
||||||
if (this->has_brightness() && this->brightness_ > 0.0f && !(color_mode & ColorCapability::BRIGHTNESS)) {
|
|
||||||
log_feature_not_supported(name, LOG_STR("brightness"));
|
|
||||||
this->set_flag_(FLAG_HAS_BRIGHTNESS, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transition length possible check
|
|
||||||
if (this->has_transition_() && this->transition_length_ != 0 && !(color_mode & ColorCapability::BRIGHTNESS)) {
|
|
||||||
log_feature_not_supported(name, LOG_STR("transitions"));
|
|
||||||
this->set_flag_(FLAG_HAS_TRANSITION, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Color brightness exists check
|
|
||||||
if (this->has_color_brightness() && this->color_brightness_ > 0.0f && !(color_mode & ColorCapability::RGB)) {
|
|
||||||
log_color_mode_not_supported(name, LOG_STR("RGB brightness"));
|
|
||||||
this->set_flag_(FLAG_HAS_COLOR_BRIGHTNESS, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// RGB exists check
|
|
||||||
if ((this->has_red() && this->red_ > 0.0f) || (this->has_green() && this->green_ > 0.0f) ||
|
|
||||||
(this->has_blue() && this->blue_ > 0.0f)) {
|
|
||||||
if (!(color_mode & ColorCapability::RGB)) {
|
|
||||||
log_color_mode_not_supported(name, LOG_STR("RGB color"));
|
|
||||||
this->set_flag_(FLAG_HAS_RED, false);
|
|
||||||
this->set_flag_(FLAG_HAS_GREEN, false);
|
|
||||||
this->set_flag_(FLAG_HAS_BLUE, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// White value exists check
|
|
||||||
if (this->has_white() && this->white_ > 0.0f &&
|
|
||||||
!(color_mode & ColorCapability::WHITE || color_mode & ColorCapability::COLD_WARM_WHITE)) {
|
|
||||||
log_color_mode_not_supported(name, LOG_STR("white value"));
|
|
||||||
this->set_flag_(FLAG_HAS_WHITE, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Color temperature exists check
|
|
||||||
if (this->has_color_temperature() &&
|
|
||||||
!(color_mode & ColorCapability::COLOR_TEMPERATURE || color_mode & ColorCapability::COLD_WARM_WHITE)) {
|
|
||||||
log_color_mode_not_supported(name, LOG_STR("color temperature"));
|
|
||||||
this->set_flag_(FLAG_HAS_COLOR_TEMPERATURE, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cold/warm white value exists check
|
|
||||||
if ((this->has_cold_white() && this->cold_white_ > 0.0f) || (this->has_warm_white() && this->warm_white_ > 0.0f)) {
|
|
||||||
if (!(color_mode & ColorCapability::COLD_WARM_WHITE)) {
|
|
||||||
log_color_mode_not_supported(name, LOG_STR("cold/warm white value"));
|
|
||||||
this->set_flag_(FLAG_HAS_COLD_WHITE, false);
|
|
||||||
this->set_flag_(FLAG_HAS_WARM_WHITE, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#define VALIDATE_RANGE_(name_, upper_name, min, max) \
|
|
||||||
if (this->has_##name_()) { \
|
|
||||||
auto val = this->name_##_; \
|
|
||||||
if (val < (min) || val > (max)) { \
|
|
||||||
log_validation_warning(name, LOG_STR(upper_name), val, (min), (max)); \
|
|
||||||
this->name_##_ = clamp(val, (min), (max)); \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
#define VALIDATE_RANGE(name, upper_name) VALIDATE_RANGE_(name, upper_name, 0.0f, 1.0f)
|
|
||||||
|
|
||||||
// Range checks
|
|
||||||
VALIDATE_RANGE(brightness, "Brightness")
|
|
||||||
VALIDATE_RANGE(color_brightness, "Color brightness")
|
|
||||||
VALIDATE_RANGE(red, "Red")
|
|
||||||
VALIDATE_RANGE(green, "Green")
|
|
||||||
VALIDATE_RANGE(blue, "Blue")
|
|
||||||
VALIDATE_RANGE(white, "White")
|
|
||||||
VALIDATE_RANGE(cold_white, "Cold white")
|
|
||||||
VALIDATE_RANGE(warm_white, "Warm white")
|
|
||||||
VALIDATE_RANGE_(color_temperature, "Color temperature", traits.get_min_mireds(), traits.get_max_mireds())
|
|
||||||
|
|
||||||
// Flag whether an explicit turn off was requested, in which case we'll also stop the effect.
|
// Flag whether an explicit turn off was requested, in which case we'll also stop the effect.
|
||||||
bool explicit_turn_off_request = this->has_state() && !this->state_;
|
bool explicit_turn_off_request = this->has_state() && !this->state_;
|
||||||
|
|
||||||
// Turn off when brightness is set to zero, and reset brightness (so that it has nonzero brightness when turned on).
|
// Turn off when brightness is set to zero, and reset brightness (so that it has nonzero brightness when turned on).
|
||||||
if (this->has_brightness() && this->brightness_ == 0.0f) {
|
if (this->has_brightness() && this->brightness_ == 0.0f) {
|
||||||
this->state_ = false;
|
this->state_ = false;
|
||||||
this->set_flag_(FLAG_HAS_STATE, true);
|
this->set_flag_(FLAG_HAS_STATE);
|
||||||
this->brightness_ = 1.0f;
|
this->brightness_ = 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set color brightness to 100% if currently zero and a color is set.
|
// Set color brightness to 100% if currently zero and a color is set.
|
||||||
if (this->has_red() || this->has_green() || this->has_blue()) {
|
if ((this->has_red() || this->has_green() || this->has_blue()) && !this->has_color_brightness() &&
|
||||||
if (!this->has_color_brightness() && this->parent_->remote_values.get_color_brightness() == 0.0f) {
|
this->parent_->remote_values.get_color_brightness() == 0.0f) {
|
||||||
this->color_brightness_ = 1.0f;
|
this->color_brightness_ = 1.0f;
|
||||||
this->set_flag_(FLAG_HAS_COLOR_BRIGHTNESS, true);
|
this->set_flag_(FLAG_HAS_COLOR_BRIGHTNESS);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create color values for the light with this call applied.
|
// Capability validation
|
||||||
|
if (this->has_brightness() && this->brightness_ > 0.0f && !(color_mode & ColorCapability::BRIGHTNESS))
|
||||||
|
this->log_and_clear_unsupported_(FLAG_HAS_BRIGHTNESS, LOG_STR("brightness"), false);
|
||||||
|
|
||||||
|
// Transition length possible check
|
||||||
|
if (this->has_transition_() && this->transition_length_ != 0 && !(color_mode & ColorCapability::BRIGHTNESS))
|
||||||
|
this->log_and_clear_unsupported_(FLAG_HAS_TRANSITION, LOG_STR("transitions"), false);
|
||||||
|
|
||||||
|
if (this->has_color_brightness() && this->color_brightness_ > 0.0f && !(color_mode & ColorCapability::RGB))
|
||||||
|
this->log_and_clear_unsupported_(FLAG_HAS_COLOR_BRIGHTNESS, LOG_STR("RGB brightness"), true);
|
||||||
|
|
||||||
|
// RGB exists check
|
||||||
|
if (((this->has_red() && this->red_ > 0.0f) || (this->has_green() && this->green_ > 0.0f) ||
|
||||||
|
(this->has_blue() && this->blue_ > 0.0f)) &&
|
||||||
|
!(color_mode & ColorCapability::RGB)) {
|
||||||
|
log_color_mode_not_supported(name, LOG_STR("RGB color"));
|
||||||
|
this->clear_flag_(FLAG_HAS_RED);
|
||||||
|
this->clear_flag_(FLAG_HAS_GREEN);
|
||||||
|
this->clear_flag_(FLAG_HAS_BLUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// White value exists check
|
||||||
|
if (this->has_white() && this->white_ > 0.0f &&
|
||||||
|
!(color_mode & ColorCapability::WHITE || color_mode & ColorCapability::COLD_WARM_WHITE))
|
||||||
|
this->log_and_clear_unsupported_(FLAG_HAS_WHITE, LOG_STR("white value"), true);
|
||||||
|
|
||||||
|
// Color temperature exists check
|
||||||
|
if (this->has_color_temperature() &&
|
||||||
|
!(color_mode & ColorCapability::COLOR_TEMPERATURE || color_mode & ColorCapability::COLD_WARM_WHITE))
|
||||||
|
this->log_and_clear_unsupported_(FLAG_HAS_COLOR_TEMPERATURE, LOG_STR("color temperature"), true);
|
||||||
|
|
||||||
|
// Cold/warm white value exists check
|
||||||
|
if (((this->has_cold_white() && this->cold_white_ > 0.0f) || (this->has_warm_white() && this->warm_white_ > 0.0f)) &&
|
||||||
|
!(color_mode & ColorCapability::COLD_WARM_WHITE)) {
|
||||||
|
log_color_mode_not_supported(name, LOG_STR("cold/warm white value"));
|
||||||
|
this->clear_flag_(FLAG_HAS_COLD_WHITE);
|
||||||
|
this->clear_flag_(FLAG_HAS_WARM_WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create color values and validate+apply ranges in one step to eliminate duplicate checks
|
||||||
auto v = this->parent_->remote_values;
|
auto v = this->parent_->remote_values;
|
||||||
if (this->has_color_mode())
|
if (this->has_color_mode())
|
||||||
v.set_color_mode(this->color_mode_);
|
v.set_color_mode(this->color_mode_);
|
||||||
if (this->has_state())
|
if (this->has_state())
|
||||||
v.set_state(this->state_);
|
v.set_state(this->state_);
|
||||||
if (this->has_brightness())
|
|
||||||
v.set_brightness(this->brightness_);
|
#define VALIDATE_AND_APPLY(field, setter, name_str, ...) \
|
||||||
if (this->has_color_brightness())
|
if (this->has_##field()) { \
|
||||||
v.set_color_brightness(this->color_brightness_);
|
clamp_and_log_if_invalid(name, this->field##_, LOG_STR(name_str), ##__VA_ARGS__); \
|
||||||
if (this->has_red())
|
v.setter(this->field##_); \
|
||||||
v.set_red(this->red_);
|
}
|
||||||
if (this->has_green())
|
|
||||||
v.set_green(this->green_);
|
VALIDATE_AND_APPLY(brightness, set_brightness, "Brightness")
|
||||||
if (this->has_blue())
|
VALIDATE_AND_APPLY(color_brightness, set_color_brightness, "Color brightness")
|
||||||
v.set_blue(this->blue_);
|
VALIDATE_AND_APPLY(red, set_red, "Red")
|
||||||
if (this->has_white())
|
VALIDATE_AND_APPLY(green, set_green, "Green")
|
||||||
v.set_white(this->white_);
|
VALIDATE_AND_APPLY(blue, set_blue, "Blue")
|
||||||
if (this->has_color_temperature())
|
VALIDATE_AND_APPLY(white, set_white, "White")
|
||||||
v.set_color_temperature(this->color_temperature_);
|
VALIDATE_AND_APPLY(cold_white, set_cold_white, "Cold white")
|
||||||
if (this->has_cold_white())
|
VALIDATE_AND_APPLY(warm_white, set_warm_white, "Warm white")
|
||||||
v.set_cold_white(this->cold_white_);
|
VALIDATE_AND_APPLY(color_temperature, set_color_temperature, "Color temperature", traits.get_min_mireds(),
|
||||||
if (this->has_warm_white())
|
traits.get_max_mireds())
|
||||||
v.set_warm_white(this->warm_white_);
|
|
||||||
|
#undef VALIDATE_AND_APPLY
|
||||||
|
|
||||||
v.normalize_color();
|
v.normalize_color();
|
||||||
|
|
||||||
// Flash length check
|
// Flash length check
|
||||||
if (this->has_flash_() && this->flash_length_ == 0) {
|
if (this->has_flash_() && this->flash_length_ == 0) {
|
||||||
log_invalid_parameter(name, LOG_STR("flash length must be greater than zero"));
|
log_invalid_parameter(name, LOG_STR("flash length must be >0"));
|
||||||
this->set_flag_(FLAG_HAS_FLASH, false);
|
this->clear_flag_(FLAG_HAS_FLASH);
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate transition length/flash length/effect not used at the same time
|
// validate transition length/flash length/effect not used at the same time
|
||||||
@@ -330,42 +310,40 @@ LightColorValues LightCall::validate_() {
|
|||||||
|
|
||||||
// If effect is already active, remove effect start
|
// If effect is already active, remove effect start
|
||||||
if (this->has_effect_() && this->effect_ == this->parent_->active_effect_index_) {
|
if (this->has_effect_() && this->effect_ == this->parent_->active_effect_index_) {
|
||||||
this->set_flag_(FLAG_HAS_EFFECT, false);
|
this->clear_flag_(FLAG_HAS_EFFECT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate effect index
|
// validate effect index
|
||||||
if (this->has_effect_() && this->effect_ > this->parent_->effects_.size()) {
|
if (this->has_effect_() && this->effect_ > this->parent_->effects_.size()) {
|
||||||
ESP_LOGW(TAG, "'%s': invalid effect index %" PRIu32, name, this->effect_);
|
ESP_LOGW(TAG, "'%s': invalid effect index %" PRIu32, name, this->effect_);
|
||||||
this->set_flag_(FLAG_HAS_EFFECT, false);
|
this->clear_flag_(FLAG_HAS_EFFECT);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) {
|
if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) {
|
||||||
log_invalid_parameter(name, LOG_STR("effect cannot be used with transition/flash"));
|
log_invalid_parameter(name, LOG_STR("effect cannot be used with transition/flash"));
|
||||||
this->set_flag_(FLAG_HAS_TRANSITION, false);
|
this->clear_flag_(FLAG_HAS_TRANSITION);
|
||||||
this->set_flag_(FLAG_HAS_FLASH, false);
|
this->clear_flag_(FLAG_HAS_FLASH);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->has_flash_() && this->has_transition_()) {
|
if (this->has_flash_() && this->has_transition_()) {
|
||||||
log_invalid_parameter(name, LOG_STR("flash cannot be used with transition"));
|
log_invalid_parameter(name, LOG_STR("flash cannot be used with transition"));
|
||||||
this->set_flag_(FLAG_HAS_TRANSITION, false);
|
this->clear_flag_(FLAG_HAS_TRANSITION);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->has_transition_() && !this->has_flash_() && (!this->has_effect_() || this->effect_ == 0) &&
|
if (!this->has_transition_() && !this->has_flash_() && (!this->has_effect_() || this->effect_ == 0) &&
|
||||||
supports_transition) {
|
supports_transition) {
|
||||||
// nothing specified and light supports transitions, set default transition length
|
// nothing specified and light supports transitions, set default transition length
|
||||||
this->transition_length_ = this->parent_->default_transition_length_;
|
this->transition_length_ = this->parent_->default_transition_length_;
|
||||||
this->set_flag_(FLAG_HAS_TRANSITION, true);
|
this->set_flag_(FLAG_HAS_TRANSITION);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->has_transition_() && this->transition_length_ == 0) {
|
if (this->has_transition_() && this->transition_length_ == 0) {
|
||||||
// 0 transition is interpreted as no transition (instant change)
|
// 0 transition is interpreted as no transition (instant change)
|
||||||
this->set_flag_(FLAG_HAS_TRANSITION, false);
|
this->clear_flag_(FLAG_HAS_TRANSITION);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->has_transition_() && !supports_transition) {
|
if (this->has_transition_() && !supports_transition)
|
||||||
log_feature_not_supported(name, LOG_STR("transitions"));
|
this->log_and_clear_unsupported_(FLAG_HAS_TRANSITION, LOG_STR("transitions"), false);
|
||||||
this->set_flag_(FLAG_HAS_TRANSITION, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not a flash and turning the light off, then disable the light
|
// If not a flash and turning the light off, then disable the light
|
||||||
// Do not use light color values directly, so that effects can set 0% brightness
|
// Do not use light color values directly, so that effects can set 0% brightness
|
||||||
@@ -374,17 +352,17 @@ LightColorValues LightCall::validate_() {
|
|||||||
if (!this->has_flash_() && !target_state) {
|
if (!this->has_flash_() && !target_state) {
|
||||||
if (this->has_effect_()) {
|
if (this->has_effect_()) {
|
||||||
log_invalid_parameter(name, LOG_STR("cannot start effect when turning off"));
|
log_invalid_parameter(name, LOG_STR("cannot start effect when turning off"));
|
||||||
this->set_flag_(FLAG_HAS_EFFECT, false);
|
this->clear_flag_(FLAG_HAS_EFFECT);
|
||||||
} else if (this->parent_->active_effect_index_ != 0 && explicit_turn_off_request) {
|
} else if (this->parent_->active_effect_index_ != 0 && explicit_turn_off_request) {
|
||||||
// Auto turn off effect
|
// Auto turn off effect
|
||||||
this->effect_ = 0;
|
this->effect_ = 0;
|
||||||
this->set_flag_(FLAG_HAS_EFFECT, true);
|
this->set_flag_(FLAG_HAS_EFFECT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable saving for flashes
|
// Disable saving for flashes
|
||||||
if (this->has_flash_())
|
if (this->has_flash_())
|
||||||
this->set_flag_(FLAG_SAVE, false);
|
this->clear_flag_(FLAG_SAVE);
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
@@ -418,12 +396,12 @@ void LightCall::transform_parameters_() {
|
|||||||
const float gamma = this->parent_->get_gamma_correct();
|
const float gamma = this->parent_->get_gamma_correct();
|
||||||
this->cold_white_ = gamma_uncorrect(cw_fraction / max_cw_ww, gamma);
|
this->cold_white_ = gamma_uncorrect(cw_fraction / max_cw_ww, gamma);
|
||||||
this->warm_white_ = gamma_uncorrect(ww_fraction / max_cw_ww, gamma);
|
this->warm_white_ = gamma_uncorrect(ww_fraction / max_cw_ww, gamma);
|
||||||
this->set_flag_(FLAG_HAS_COLD_WHITE, true);
|
this->set_flag_(FLAG_HAS_COLD_WHITE);
|
||||||
this->set_flag_(FLAG_HAS_WARM_WHITE, true);
|
this->set_flag_(FLAG_HAS_WARM_WHITE);
|
||||||
}
|
}
|
||||||
if (this->has_white()) {
|
if (this->has_white()) {
|
||||||
this->brightness_ = this->white_;
|
this->brightness_ = this->white_;
|
||||||
this->set_flag_(FLAG_HAS_BRIGHTNESS, true);
|
this->set_flag_(FLAG_HAS_BRIGHTNESS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -630,7 +608,7 @@ LightCall &LightCall::set_effect(optional<std::string> effect) {
|
|||||||
}
|
}
|
||||||
LightCall &LightCall::set_effect(uint32_t effect_number) {
|
LightCall &LightCall::set_effect(uint32_t effect_number) {
|
||||||
this->effect_ = effect_number;
|
this->effect_ = effect_number;
|
||||||
this->set_flag_(FLAG_HAS_EFFECT, true);
|
this->set_flag_(FLAG_HAS_EFFECT);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
LightCall &LightCall::set_effect(optional<uint32_t> effect_number) {
|
LightCall &LightCall::set_effect(optional<uint32_t> effect_number) {
|
||||||
|
@@ -4,6 +4,10 @@
|
|||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
|
||||||
|
// Forward declaration
|
||||||
|
struct LogString;
|
||||||
|
|
||||||
namespace light {
|
namespace light {
|
||||||
|
|
||||||
class LightState;
|
class LightState;
|
||||||
@@ -207,14 +211,14 @@ class LightCall {
|
|||||||
FLAG_SAVE = 1 << 15,
|
FLAG_SAVE = 1 << 15,
|
||||||
};
|
};
|
||||||
|
|
||||||
bool has_transition_() { return (this->flags_ & FLAG_HAS_TRANSITION) != 0; }
|
inline bool has_transition_() { return (this->flags_ & FLAG_HAS_TRANSITION) != 0; }
|
||||||
bool has_flash_() { return (this->flags_ & FLAG_HAS_FLASH) != 0; }
|
inline bool has_flash_() { return (this->flags_ & FLAG_HAS_FLASH) != 0; }
|
||||||
bool has_effect_() { return (this->flags_ & FLAG_HAS_EFFECT) != 0; }
|
inline bool has_effect_() { return (this->flags_ & FLAG_HAS_EFFECT) != 0; }
|
||||||
bool get_publish_() { return (this->flags_ & FLAG_PUBLISH) != 0; }
|
inline bool get_publish_() { return (this->flags_ & FLAG_PUBLISH) != 0; }
|
||||||
bool get_save_() { return (this->flags_ & FLAG_SAVE) != 0; }
|
inline bool get_save_() { return (this->flags_ & FLAG_SAVE) != 0; }
|
||||||
|
|
||||||
// Helper to set flag
|
// Helper to set flag - defaults to true for common case
|
||||||
void set_flag_(FieldFlags flag, bool value) {
|
void set_flag_(FieldFlags flag, bool value = true) {
|
||||||
if (value) {
|
if (value) {
|
||||||
this->flags_ |= flag;
|
this->flags_ |= flag;
|
||||||
} else {
|
} else {
|
||||||
@@ -222,6 +226,12 @@ class LightCall {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper to clear flag - reduces code size for common case
|
||||||
|
void clear_flag_(FieldFlags flag) { this->flags_ &= ~flag; }
|
||||||
|
|
||||||
|
// Helper to log unsupported feature and clear flag - reduces code duplication
|
||||||
|
void log_and_clear_unsupported_(FieldFlags flag, const LogString *feature, bool use_color_mode_log);
|
||||||
|
|
||||||
LightState *parent_;
|
LightState *parent_;
|
||||||
|
|
||||||
// Light state values - use flags_ to check if a value has been set.
|
// Light state values - use flags_ to check if a value has been set.
|
||||||
|
@@ -74,6 +74,9 @@ FILTER_PLATFORMIO_LINES = [
|
|||||||
r"Creating BIN file .*",
|
r"Creating BIN file .*",
|
||||||
r"Warning! Could not find file \".*.crt\"",
|
r"Warning! Could not find file \".*.crt\"",
|
||||||
r"Warning! Arduino framework as an ESP-IDF component doesn't handle the `variant` field! The default `esp32` variant will be used.",
|
r"Warning! Arduino framework as an ESP-IDF component doesn't handle the `variant` field! The default `esp32` variant will be used.",
|
||||||
|
r"Warning: DEPRECATED: 'esptool.py' is deprecated. Please use 'esptool' instead. The '.py' suffix will be removed in a future major release.",
|
||||||
|
r"Warning: esp-idf-size exited with code 2",
|
||||||
|
r"esp_idf_size: error: unrecognized arguments: --ng",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@@ -147,7 +147,7 @@ lib_deps =
|
|||||||
makuna/NeoPixelBus@2.8.0 ; neopixelbus
|
makuna/NeoPixelBus@2.8.0 ; neopixelbus
|
||||||
esphome/ESP32-audioI2S@2.3.0 ; i2s_audio
|
esphome/ESP32-audioI2S@2.3.0 ; i2s_audio
|
||||||
droscy/esp_wireguard@0.4.2 ; wireguard
|
droscy/esp_wireguard@0.4.2 ; wireguard
|
||||||
esphome/esp-audio-libs@1.1.4 ; audio
|
esphome/esp-audio-libs@2.0.1 ; audio
|
||||||
|
|
||||||
build_flags =
|
build_flags =
|
||||||
${common:arduino.build_flags}
|
${common:arduino.build_flags}
|
||||||
@@ -170,7 +170,7 @@ lib_deps =
|
|||||||
${common:idf.lib_deps}
|
${common:idf.lib_deps}
|
||||||
droscy/esp_wireguard@0.4.2 ; wireguard
|
droscy/esp_wireguard@0.4.2 ; wireguard
|
||||||
kahrendt/ESPMicroSpeechFeatures@1.1.0 ; micro_wake_word
|
kahrendt/ESPMicroSpeechFeatures@1.1.0 ; micro_wake_word
|
||||||
esphome/esp-audio-libs@1.1.4 ; audio
|
esphome/esp-audio-libs@2.0.1 ; audio
|
||||||
build_flags =
|
build_flags =
|
||||||
${common:idf.build_flags}
|
${common:idf.build_flags}
|
||||||
-Wno-nonnull-compare
|
-Wno-nonnull-compare
|
||||||
|
@@ -15,7 +15,7 @@ async def test_oversized_payload_plaintext(
|
|||||||
run_compiled: RunCompiledFunction,
|
run_compiled: RunCompiledFunction,
|
||||||
api_client_connected_with_disconnect: APIClientConnectedWithDisconnectFactory,
|
api_client_connected_with_disconnect: APIClientConnectedWithDisconnectFactory,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that oversized payloads (>100KiB) from client cause disconnection without crashing."""
|
"""Test that oversized payloads (>32768 bytes) from client cause disconnection without crashing."""
|
||||||
process_exited = False
|
process_exited = False
|
||||||
helper_log_found = False
|
helper_log_found = False
|
||||||
|
|
||||||
@@ -39,8 +39,8 @@ async def test_oversized_payload_plaintext(
|
|||||||
assert device_info is not None
|
assert device_info is not None
|
||||||
assert device_info.name == "oversized-plaintext"
|
assert device_info.name == "oversized-plaintext"
|
||||||
|
|
||||||
# Create an oversized payload (>100KiB)
|
# Create an oversized payload (>32768 bytes which is our new limit)
|
||||||
oversized_data = b"X" * (100 * 1024 + 1) # 100KiB + 1 byte
|
oversized_data = b"X" * 40000 # ~40KiB, exceeds the 32768 byte limit
|
||||||
|
|
||||||
# Access the internal connection to send raw data
|
# Access the internal connection to send raw data
|
||||||
frame_helper = client._connection._frame_helper
|
frame_helper = client._connection._frame_helper
|
||||||
@@ -132,22 +132,24 @@ async def test_oversized_payload_noise(
|
|||||||
run_compiled: RunCompiledFunction,
|
run_compiled: RunCompiledFunction,
|
||||||
api_client_connected_with_disconnect: APIClientConnectedWithDisconnectFactory,
|
api_client_connected_with_disconnect: APIClientConnectedWithDisconnectFactory,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that oversized payloads (>100KiB) from client cause disconnection without crashing with noise encryption."""
|
"""Test that oversized payloads from client cause disconnection without crashing with noise encryption."""
|
||||||
noise_key = "N4Yle5YirwZhPiHHsdZLdOA73ndj/84veVaLhTvxCuU="
|
noise_key = "N4Yle5YirwZhPiHHsdZLdOA73ndj/84veVaLhTvxCuU="
|
||||||
process_exited = False
|
process_exited = False
|
||||||
cipherstate_failed = False
|
helper_log_found = False
|
||||||
|
|
||||||
def check_logs(line: str) -> None:
|
def check_logs(line: str) -> None:
|
||||||
nonlocal process_exited, cipherstate_failed
|
nonlocal process_exited, helper_log_found
|
||||||
# Check for signs that the process exited/crashed
|
# Check for signs that the process exited/crashed
|
||||||
if "Segmentation fault" in line or "core dumped" in line:
|
if "Segmentation fault" in line or "core dumped" in line:
|
||||||
process_exited = True
|
process_exited = True
|
||||||
# Check for the expected warning about decryption failure
|
# Check for HELPER_LOG message about message size exceeding maximum
|
||||||
|
# With our new protection, oversized messages are rejected at frame level
|
||||||
if (
|
if (
|
||||||
"[W][api.connection" in line
|
"[VV]" in line
|
||||||
and "Reading failed CIPHERSTATE_DECRYPT_FAILED" in line
|
and "Bad packet: message size" in line
|
||||||
|
and "exceeds maximum" in line
|
||||||
):
|
):
|
||||||
cipherstate_failed = True
|
helper_log_found = True
|
||||||
|
|
||||||
async with run_compiled(yaml_config, line_callback=check_logs):
|
async with run_compiled(yaml_config, line_callback=check_logs):
|
||||||
async with api_client_connected_with_disconnect(noise_psk=noise_key) as (
|
async with api_client_connected_with_disconnect(noise_psk=noise_key) as (
|
||||||
@@ -159,8 +161,8 @@ async def test_oversized_payload_noise(
|
|||||||
assert device_info is not None
|
assert device_info is not None
|
||||||
assert device_info.name == "oversized-noise"
|
assert device_info.name == "oversized-noise"
|
||||||
|
|
||||||
# Create an oversized payload (>100KiB)
|
# Create an oversized payload (>32768 bytes which is our new limit)
|
||||||
oversized_data = b"Y" * (100 * 1024 + 1) # 100KiB + 1 byte
|
oversized_data = b"Y" * 40000 # ~40KiB, exceeds the 32768 byte limit
|
||||||
|
|
||||||
# Access the internal connection to send raw data
|
# Access the internal connection to send raw data
|
||||||
frame_helper = client._connection._frame_helper
|
frame_helper = client._connection._frame_helper
|
||||||
@@ -175,9 +177,9 @@ async def test_oversized_payload_noise(
|
|||||||
|
|
||||||
# After disconnection, verify process didn't crash
|
# After disconnection, verify process didn't crash
|
||||||
assert not process_exited, "ESPHome process should not crash"
|
assert not process_exited, "ESPHome process should not crash"
|
||||||
# Verify we saw the expected warning message
|
# Verify we saw the expected HELPER_LOG message
|
||||||
assert cipherstate_failed, (
|
assert helper_log_found, (
|
||||||
"Expected to see warning about CIPHERSTATE_DECRYPT_FAILED"
|
"Expected to see HELPER_LOG about message size exceeding maximum"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Try to reconnect to verify the process is still running
|
# Try to reconnect to verify the process is still running
|
||||||
|
Reference in New Issue
Block a user