diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index cdeabb5cac..64abcf2e26 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -281,23 +281,34 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess const uint8_t header_padding = conn->helper_->frame_header_padding(); const uint8_t footer_size = conn->helper_->frame_footer_size(); - // Calculate total size with padding for buffer allocation - size_t total_calculated_size = calculated_size + header_padding + footer_size; + // Calculate total size with padding + size_t total_size = calculated_size + header_padding + footer_size; // Check if it fits - if (total_calculated_size > remaining_size) { + if (total_size > remaining_size) { return 0; // Doesn't fit } - // Allocate buffer space - pass payload size, allocation functions add header/footer space - ProtoWriteBuffer buffer = is_single ? conn->allocate_single_message_buffer(calculated_size) - : conn->allocate_batch_message_buffer(calculated_size); - - // Get buffer size after allocation (which includes header padding) + // Get buffer and prepare it inline std::vector &shared_buf = conn->parent_->get_shared_buffer_ref(); - size_t size_before_encode = shared_buf.size(); + + if (is_single || conn->flags_.batch_first_message) { + // Single message or first batch message + conn->prepare_first_message_buffer(shared_buf, header_padding, total_size); + if (conn->flags_.batch_first_message) { + conn->flags_.batch_first_message = false; + } + } else { + // Batch message second or later + // Add padding for previous message footer + this message header + size_t current_size = shared_buf.size(); + shared_buf.reserve(current_size + total_size); + shared_buf.resize(current_size + footer_size + header_padding); + } // Encode directly into buffer + ProtoWriteBuffer buffer{&shared_buf}; + size_t size_before_encode = shared_buf.size(); msg.encode(buffer); // Calculate actual encoded size (not including header that was already added) @@ -1620,14 +1631,6 @@ bool APIConnection::schedule_batch_() { return true; } -ProtoWriteBuffer APIConnection::allocate_single_message_buffer(uint16_t size) { return this->create_buffer(size); } - -ProtoWriteBuffer APIConnection::allocate_batch_message_buffer(uint16_t size) { - ProtoWriteBuffer result = this->prepare_message_buffer(size, this->flags_.batch_first_message); - this->flags_.batch_first_message = false; - return result; -} - void APIConnection::process_batch_() { // Ensure PacketInfo remains trivially destructible for our placement new approach static_assert(std::is_trivially_destructible::value, @@ -1735,7 +1738,7 @@ void APIConnection::process_batch_() { } remaining_size -= payload_size; // Calculate where the next message's header padding will start - // Current buffer size + footer space (that prepare_message_buffer will add for this message) + // Current buffer size + footer space for this message current_offset = shared_buf.size() + footer_size; } diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index f0f308c248..076dccfad7 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -252,44 +252,21 @@ class APIConnection : public APIServerConnection { // Get header padding size - used for both reserve and insert uint8_t header_padding = this->helper_->frame_header_padding(); - // Get shared buffer from parent server std::vector &shared_buf = this->parent_->get_shared_buffer_ref(); + this->prepare_first_message_buffer(shared_buf, header_padding, + reserve_size + header_padding + this->helper_->frame_footer_size()); + return {&shared_buf}; + } + + void prepare_first_message_buffer(std::vector &shared_buf, size_t header_padding, size_t total_size) { shared_buf.clear(); // Reserve space for header padding + message + footer // - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext) // - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext) - shared_buf.reserve(reserve_size + header_padding + this->helper_->frame_footer_size()); + shared_buf.reserve(total_size); // Resize to add header padding so message encoding starts at the correct position shared_buf.resize(header_padding); - return {&shared_buf}; - } - - // Prepare buffer for next message in batch - ProtoWriteBuffer prepare_message_buffer(uint16_t message_size, bool is_first_message) { - // Get reference to shared buffer (it maintains state between batch messages) - std::vector &shared_buf = this->parent_->get_shared_buffer_ref(); - - if (is_first_message) { - shared_buf.clear(); - } - - size_t current_size = shared_buf.size(); - - // Calculate padding to add: - // - First message: just header padding - // - Subsequent messages: footer for previous message + header padding for this message - size_t padding_to_add = is_first_message - ? this->helper_->frame_header_padding() - : this->helper_->frame_header_padding() + this->helper_->frame_footer_size(); - - // Reserve space for padding + message - shared_buf.reserve(current_size + padding_to_add + message_size); - - // Resize to add the padding bytes - shared_buf.resize(current_size + padding_to_add); - - return {&shared_buf}; } bool try_to_clear_buffer(bool log_out_of_space); @@ -297,10 +274,6 @@ class APIConnection : public APIServerConnection { std::string get_client_combined_info() const { return this->client_info_.get_combined_info(); } - // Buffer allocator methods for batch processing - ProtoWriteBuffer allocate_single_message_buffer(uint16_t size); - ProtoWriteBuffer allocate_batch_message_buffer(uint16_t size); - protected: // Helper function to handle authentication completion void complete_authentication_(); diff --git a/esphome/components/espnow/espnow_component.cpp b/esphome/components/espnow/espnow_component.cpp index 82f8e3230e..b0d5938dba 100644 --- a/esphome/components/espnow/espnow_component.cpp +++ b/esphome/components/espnow/espnow_component.cpp @@ -208,11 +208,11 @@ void ESPNowComponent::enable_() { esp_wifi_connectionless_module_set_wake_interval(CONFIG_ESPNOW_WAKE_INTERVAL); #endif + this->state_ = ESPNOW_STATE_ENABLED; + for (auto peer : this->peers_) { this->add_peer(peer.address); } - - this->state_ = ESPNOW_STATE_ENABLED; } void ESPNowComponent::disable() { @@ -407,7 +407,7 @@ esp_err_t ESPNowComponent::add_peer(const uint8_t *peer) { } if (memcmp(peer, this->own_address_, ESP_NOW_ETH_ALEN) == 0) { - this->mark_failed(); + this->status_momentary_warning("peer-add-failed"); return ESP_ERR_INVALID_MAC; } diff --git a/esphome/components/psram/__init__.py b/esphome/components/psram/__init__.py index fd7e70a055..b5c87ae5a8 100644 --- a/esphome/components/psram/__init__.py +++ b/esphome/components/psram/__init__.py @@ -16,6 +16,7 @@ from esphome.components.esp32.const import ( import esphome.config_validation as cv from esphome.const import ( CONF_ADVANCED, + CONF_DISABLED, CONF_FRAMEWORK, CONF_ID, CONF_MODE, @@ -102,6 +103,7 @@ def get_config_schema(config): cv.Optional(CONF_MODE, default=modes[0]): cv.one_of(*modes, lower=True), cv.Optional(CONF_ENABLE_ECC, default=False): cv.boolean, cv.Optional(CONF_SPEED, default=speeds[0]): cv.one_of(*speeds, upper=True), + cv.Optional(CONF_DISABLED, default=False): cv.boolean, } )(config) @@ -112,6 +114,8 @@ FINAL_VALIDATE_SCHEMA = validate_psram_mode async def to_code(config): + if config[CONF_DISABLED]: + return if CORE.using_arduino: cg.add_build_flag("-DBOARD_HAS_PSRAM") if config[CONF_MODE] == TYPE_OCTAL: diff --git a/esphome/components/qmp6988/qmp6988.cpp b/esphome/components/qmp6988/qmp6988.cpp index 6c22150f4f..61fde186d7 100644 --- a/esphome/components/qmp6988/qmp6988.cpp +++ b/esphome/components/qmp6988/qmp6988.cpp @@ -18,14 +18,6 @@ static const uint8_t QMP6988_TEMPERATURE_MSB_REG = 0xFA; /* Temperature MSB Reg static const uint8_t QMP6988_CALIBRATION_DATA_START = 0xA0; /* QMP6988 compensation coefficients */ static const uint8_t QMP6988_CALIBRATION_DATA_LENGTH = 25; -static const uint8_t SHIFT_RIGHT_4_POSITION = 4; -static const uint8_t SHIFT_LEFT_2_POSITION = 2; -static const uint8_t SHIFT_LEFT_4_POSITION = 4; -static const uint8_t SHIFT_LEFT_5_POSITION = 5; -static const uint8_t SHIFT_LEFT_8_POSITION = 8; -static const uint8_t SHIFT_LEFT_12_POSITION = 12; -static const uint8_t SHIFT_LEFT_16_POSITION = 16; - /* power mode */ static const uint8_t QMP6988_SLEEP_MODE = 0x00; static const uint8_t QMP6988_FORCED_MODE = 0x01; @@ -95,64 +87,45 @@ static const char *iir_filter_to_str(QMP6988IIRFilter filter) { } bool QMP6988Component::device_check_() { - uint8_t ret = 0; - - ret = this->read_register(QMP6988_CHIP_ID_REG, &(qmp6988_data_.chip_id), 1); - if (ret != i2c::ERROR_OK) { - ESP_LOGE(TAG, "%s: read chip ID (0xD1) failed", __func__); + if (this->read_register(QMP6988_CHIP_ID_REG, &(qmp6988_data_.chip_id), 1) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Read chip ID (0xD1) failed"); + return false; } - ESP_LOGD(TAG, "qmp6988 read chip id = 0x%x", qmp6988_data_.chip_id); + ESP_LOGV(TAG, "Read chip ID = 0x%x", qmp6988_data_.chip_id); return qmp6988_data_.chip_id == QMP6988_CHIP_ID; } bool QMP6988Component::get_calibration_data_() { - uint8_t status = 0; // BITFIELDS temp_COE; uint8_t a_data_uint8_tr[QMP6988_CALIBRATION_DATA_LENGTH] = {0}; - int len; - for (len = 0; len < QMP6988_CALIBRATION_DATA_LENGTH; len += 1) { - status = this->read_register(QMP6988_CALIBRATION_DATA_START + len, &a_data_uint8_tr[len], 1); - if (status != i2c::ERROR_OK) { - ESP_LOGE(TAG, "qmp6988 read calibration data (0xA0) error!"); + for (uint8_t len = 0; len < QMP6988_CALIBRATION_DATA_LENGTH; len += 1) { + if (this->read_register(QMP6988_CALIBRATION_DATA_START + len, &a_data_uint8_tr[len], 1) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Read calibration data (0xA0) error"); return false; } } qmp6988_data_.qmp6988_cali.COE_a0 = - (QMP6988_S32_t) (((a_data_uint8_tr[18] << SHIFT_LEFT_12_POSITION) | - (a_data_uint8_tr[19] << SHIFT_LEFT_4_POSITION) | (a_data_uint8_tr[24] & 0x0f)) - << 12); + (int32_t) encode_uint32(a_data_uint8_tr[18], a_data_uint8_tr[19], (a_data_uint8_tr[24] & 0x0f) << 4, 0); qmp6988_data_.qmp6988_cali.COE_a0 = qmp6988_data_.qmp6988_cali.COE_a0 >> 12; - qmp6988_data_.qmp6988_cali.COE_a1 = - (QMP6988_S16_t) (((a_data_uint8_tr[20]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[21]); - qmp6988_data_.qmp6988_cali.COE_a2 = - (QMP6988_S16_t) (((a_data_uint8_tr[22]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[23]); + qmp6988_data_.qmp6988_cali.COE_a1 = (int16_t) encode_uint16(a_data_uint8_tr[20], a_data_uint8_tr[21]); + qmp6988_data_.qmp6988_cali.COE_a2 = (int16_t) encode_uint16(a_data_uint8_tr[22], a_data_uint8_tr[23]); qmp6988_data_.qmp6988_cali.COE_b00 = - (QMP6988_S32_t) (((a_data_uint8_tr[0] << SHIFT_LEFT_12_POSITION) | (a_data_uint8_tr[1] << SHIFT_LEFT_4_POSITION) | - ((a_data_uint8_tr[24] & 0xf0) >> SHIFT_RIGHT_4_POSITION)) - << 12); + (int32_t) encode_uint32(a_data_uint8_tr[0], a_data_uint8_tr[1], a_data_uint8_tr[24] & 0xf0, 0); qmp6988_data_.qmp6988_cali.COE_b00 = qmp6988_data_.qmp6988_cali.COE_b00 >> 12; - qmp6988_data_.qmp6988_cali.COE_bt1 = - (QMP6988_S16_t) (((a_data_uint8_tr[2]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[3]); - qmp6988_data_.qmp6988_cali.COE_bt2 = - (QMP6988_S16_t) (((a_data_uint8_tr[4]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[5]); - qmp6988_data_.qmp6988_cali.COE_bp1 = - (QMP6988_S16_t) (((a_data_uint8_tr[6]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[7]); - qmp6988_data_.qmp6988_cali.COE_b11 = - (QMP6988_S16_t) (((a_data_uint8_tr[8]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[9]); - qmp6988_data_.qmp6988_cali.COE_bp2 = - (QMP6988_S16_t) (((a_data_uint8_tr[10]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[11]); - qmp6988_data_.qmp6988_cali.COE_b12 = - (QMP6988_S16_t) (((a_data_uint8_tr[12]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[13]); - qmp6988_data_.qmp6988_cali.COE_b21 = - (QMP6988_S16_t) (((a_data_uint8_tr[14]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[15]); - qmp6988_data_.qmp6988_cali.COE_bp3 = - (QMP6988_S16_t) (((a_data_uint8_tr[16]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[17]); + qmp6988_data_.qmp6988_cali.COE_bt1 = (int16_t) encode_uint16(a_data_uint8_tr[2], a_data_uint8_tr[3]); + qmp6988_data_.qmp6988_cali.COE_bt2 = (int16_t) encode_uint16(a_data_uint8_tr[4], a_data_uint8_tr[5]); + qmp6988_data_.qmp6988_cali.COE_bp1 = (int16_t) encode_uint16(a_data_uint8_tr[6], a_data_uint8_tr[7]); + qmp6988_data_.qmp6988_cali.COE_b11 = (int16_t) encode_uint16(a_data_uint8_tr[8], a_data_uint8_tr[9]); + qmp6988_data_.qmp6988_cali.COE_bp2 = (int16_t) encode_uint16(a_data_uint8_tr[10], a_data_uint8_tr[11]); + qmp6988_data_.qmp6988_cali.COE_b12 = (int16_t) encode_uint16(a_data_uint8_tr[12], a_data_uint8_tr[13]); + qmp6988_data_.qmp6988_cali.COE_b21 = (int16_t) encode_uint16(a_data_uint8_tr[14], a_data_uint8_tr[15]); + qmp6988_data_.qmp6988_cali.COE_bp3 = (int16_t) encode_uint16(a_data_uint8_tr[16], a_data_uint8_tr[17]); ESP_LOGV(TAG, "<-----------calibration data-------------->\r\n"); ESP_LOGV(TAG, "COE_a0[%d] COE_a1[%d] COE_a2[%d] COE_b00[%d]\r\n", qmp6988_data_.qmp6988_cali.COE_a0, @@ -166,17 +139,17 @@ bool QMP6988Component::get_calibration_data_() { qmp6988_data_.ik.a0 = qmp6988_data_.qmp6988_cali.COE_a0; // 20Q4 qmp6988_data_.ik.b00 = qmp6988_data_.qmp6988_cali.COE_b00; // 20Q4 - qmp6988_data_.ik.a1 = 3608L * (QMP6988_S32_t) qmp6988_data_.qmp6988_cali.COE_a1 - 1731677965L; // 31Q23 - qmp6988_data_.ik.a2 = 16889L * (QMP6988_S32_t) qmp6988_data_.qmp6988_cali.COE_a2 - 87619360L; // 30Q47 + qmp6988_data_.ik.a1 = 3608L * (int32_t) qmp6988_data_.qmp6988_cali.COE_a1 - 1731677965L; // 31Q23 + qmp6988_data_.ik.a2 = 16889L * (int32_t) qmp6988_data_.qmp6988_cali.COE_a2 - 87619360L; // 30Q47 - qmp6988_data_.ik.bt1 = 2982L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bt1 + 107370906L; // 28Q15 - qmp6988_data_.ik.bt2 = 329854L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bt2 + 108083093L; // 34Q38 - qmp6988_data_.ik.bp1 = 19923L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bp1 + 1133836764L; // 31Q20 - qmp6988_data_.ik.b11 = 2406L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_b11 + 118215883L; // 28Q34 - qmp6988_data_.ik.bp2 = 3079L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bp2 - 181579595L; // 29Q43 - qmp6988_data_.ik.b12 = 6846L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_b12 + 85590281L; // 29Q53 - qmp6988_data_.ik.b21 = 13836L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_b21 + 79333336L; // 29Q60 - qmp6988_data_.ik.bp3 = 2915L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bp3 + 157155561L; // 28Q65 + qmp6988_data_.ik.bt1 = 2982L * (int64_t) qmp6988_data_.qmp6988_cali.COE_bt1 + 107370906L; // 28Q15 + qmp6988_data_.ik.bt2 = 329854L * (int64_t) qmp6988_data_.qmp6988_cali.COE_bt2 + 108083093L; // 34Q38 + qmp6988_data_.ik.bp1 = 19923L * (int64_t) qmp6988_data_.qmp6988_cali.COE_bp1 + 1133836764L; // 31Q20 + qmp6988_data_.ik.b11 = 2406L * (int64_t) qmp6988_data_.qmp6988_cali.COE_b11 + 118215883L; // 28Q34 + qmp6988_data_.ik.bp2 = 3079L * (int64_t) qmp6988_data_.qmp6988_cali.COE_bp2 - 181579595L; // 29Q43 + qmp6988_data_.ik.b12 = 6846L * (int64_t) qmp6988_data_.qmp6988_cali.COE_b12 + 85590281L; // 29Q53 + qmp6988_data_.ik.b21 = 13836L * (int64_t) qmp6988_data_.qmp6988_cali.COE_b21 + 79333336L; // 29Q60 + qmp6988_data_.ik.bp3 = 2915L * (int64_t) qmp6988_data_.qmp6988_cali.COE_bp3 + 157155561L; // 28Q65 ESP_LOGV(TAG, "<----------- int calibration data -------------->\r\n"); ESP_LOGV(TAG, "a0[%d] a1[%d] a2[%d] b00[%d]\r\n", qmp6988_data_.ik.a0, qmp6988_data_.ik.a1, qmp6988_data_.ik.a2, qmp6988_data_.ik.b00); @@ -188,55 +161,55 @@ bool QMP6988Component::get_calibration_data_() { return true; } -QMP6988_S16_t QMP6988Component::get_compensated_temperature_(qmp6988_ik_data_t *ik, QMP6988_S32_t dt) { - QMP6988_S16_t ret; - QMP6988_S64_t wk1, wk2; +int16_t QMP6988Component::get_compensated_temperature_(qmp6988_ik_data_t *ik, int32_t dt) { + int16_t ret; + int64_t wk1, wk2; // wk1: 60Q4 // bit size - wk1 = ((QMP6988_S64_t) ik->a1 * (QMP6988_S64_t) dt); // 31Q23+24-1=54 (54Q23) - wk2 = ((QMP6988_S64_t) ik->a2 * (QMP6988_S64_t) dt) >> 14; // 30Q47+24-1=53 (39Q33) - wk2 = (wk2 * (QMP6988_S64_t) dt) >> 10; // 39Q33+24-1=62 (52Q23) - wk2 = ((wk1 + wk2) / 32767) >> 19; // 54,52->55Q23 (20Q04) - ret = (QMP6988_S16_t) ((ik->a0 + wk2) >> 4); // 21Q4 -> 17Q0 + wk1 = ((int64_t) ik->a1 * (int64_t) dt); // 31Q23+24-1=54 (54Q23) + wk2 = ((int64_t) ik->a2 * (int64_t) dt) >> 14; // 30Q47+24-1=53 (39Q33) + wk2 = (wk2 * (int64_t) dt) >> 10; // 39Q33+24-1=62 (52Q23) + wk2 = ((wk1 + wk2) / 32767) >> 19; // 54,52->55Q23 (20Q04) + ret = (int16_t) ((ik->a0 + wk2) >> 4); // 21Q4 -> 17Q0 return ret; } -QMP6988_S32_t QMP6988Component::get_compensated_pressure_(qmp6988_ik_data_t *ik, QMP6988_S32_t dp, QMP6988_S16_t tx) { - QMP6988_S32_t ret; - QMP6988_S64_t wk1, wk2, wk3; +int32_t QMP6988Component::get_compensated_pressure_(qmp6988_ik_data_t *ik, int32_t dp, int16_t tx) { + int32_t ret; + int64_t wk1, wk2, wk3; // wk1 = 48Q16 // bit size - wk1 = ((QMP6988_S64_t) ik->bt1 * (QMP6988_S64_t) tx); // 28Q15+16-1=43 (43Q15) - wk2 = ((QMP6988_S64_t) ik->bp1 * (QMP6988_S64_t) dp) >> 5; // 31Q20+24-1=54 (49Q15) - wk1 += wk2; // 43,49->50Q15 - wk2 = ((QMP6988_S64_t) ik->bt2 * (QMP6988_S64_t) tx) >> 1; // 34Q38+16-1=49 (48Q37) - wk2 = (wk2 * (QMP6988_S64_t) tx) >> 8; // 48Q37+16-1=63 (55Q29) - wk3 = wk2; // 55Q29 - wk2 = ((QMP6988_S64_t) ik->b11 * (QMP6988_S64_t) tx) >> 4; // 28Q34+16-1=43 (39Q30) - wk2 = (wk2 * (QMP6988_S64_t) dp) >> 1; // 39Q30+24-1=62 (61Q29) - wk3 += wk2; // 55,61->62Q29 - wk2 = ((QMP6988_S64_t) ik->bp2 * (QMP6988_S64_t) dp) >> 13; // 29Q43+24-1=52 (39Q30) - wk2 = (wk2 * (QMP6988_S64_t) dp) >> 1; // 39Q30+24-1=62 (61Q29) - wk3 += wk2; // 62,61->63Q29 - wk1 += wk3 >> 14; // Q29 >> 14 -> Q15 - wk2 = ((QMP6988_S64_t) ik->b12 * (QMP6988_S64_t) tx); // 29Q53+16-1=45 (45Q53) - wk2 = (wk2 * (QMP6988_S64_t) tx) >> 22; // 45Q53+16-1=61 (39Q31) - wk2 = (wk2 * (QMP6988_S64_t) dp) >> 1; // 39Q31+24-1=62 (61Q30) - wk3 = wk2; // 61Q30 - wk2 = ((QMP6988_S64_t) ik->b21 * (QMP6988_S64_t) tx) >> 6; // 29Q60+16-1=45 (39Q54) - wk2 = (wk2 * (QMP6988_S64_t) dp) >> 23; // 39Q54+24-1=62 (39Q31) - wk2 = (wk2 * (QMP6988_S64_t) dp) >> 1; // 39Q31+24-1=62 (61Q20) - wk3 += wk2; // 61,61->62Q30 - wk2 = ((QMP6988_S64_t) ik->bp3 * (QMP6988_S64_t) dp) >> 12; // 28Q65+24-1=51 (39Q53) - wk2 = (wk2 * (QMP6988_S64_t) dp) >> 23; // 39Q53+24-1=62 (39Q30) - wk2 = (wk2 * (QMP6988_S64_t) dp); // 39Q30+24-1=62 (62Q30) - wk3 += wk2; // 62,62->63Q30 - wk1 += wk3 >> 15; // Q30 >> 15 = Q15 + wk1 = ((int64_t) ik->bt1 * (int64_t) tx); // 28Q15+16-1=43 (43Q15) + wk2 = ((int64_t) ik->bp1 * (int64_t) dp) >> 5; // 31Q20+24-1=54 (49Q15) + wk1 += wk2; // 43,49->50Q15 + wk2 = ((int64_t) ik->bt2 * (int64_t) tx) >> 1; // 34Q38+16-1=49 (48Q37) + wk2 = (wk2 * (int64_t) tx) >> 8; // 48Q37+16-1=63 (55Q29) + wk3 = wk2; // 55Q29 + wk2 = ((int64_t) ik->b11 * (int64_t) tx) >> 4; // 28Q34+16-1=43 (39Q30) + wk2 = (wk2 * (int64_t) dp) >> 1; // 39Q30+24-1=62 (61Q29) + wk3 += wk2; // 55,61->62Q29 + wk2 = ((int64_t) ik->bp2 * (int64_t) dp) >> 13; // 29Q43+24-1=52 (39Q30) + wk2 = (wk2 * (int64_t) dp) >> 1; // 39Q30+24-1=62 (61Q29) + wk3 += wk2; // 62,61->63Q29 + wk1 += wk3 >> 14; // Q29 >> 14 -> Q15 + wk2 = ((int64_t) ik->b12 * (int64_t) tx); // 29Q53+16-1=45 (45Q53) + wk2 = (wk2 * (int64_t) tx) >> 22; // 45Q53+16-1=61 (39Q31) + wk2 = (wk2 * (int64_t) dp) >> 1; // 39Q31+24-1=62 (61Q30) + wk3 = wk2; // 61Q30 + wk2 = ((int64_t) ik->b21 * (int64_t) tx) >> 6; // 29Q60+16-1=45 (39Q54) + wk2 = (wk2 * (int64_t) dp) >> 23; // 39Q54+24-1=62 (39Q31) + wk2 = (wk2 * (int64_t) dp) >> 1; // 39Q31+24-1=62 (61Q20) + wk3 += wk2; // 61,61->62Q30 + wk2 = ((int64_t) ik->bp3 * (int64_t) dp) >> 12; // 28Q65+24-1=51 (39Q53) + wk2 = (wk2 * (int64_t) dp) >> 23; // 39Q53+24-1=62 (39Q30) + wk2 = (wk2 * (int64_t) dp); // 39Q30+24-1=62 (62Q30) + wk3 += wk2; // 62,62->63Q30 + wk1 += wk3 >> 15; // Q30 >> 15 = Q15 wk1 /= 32767L; wk1 >>= 11; // Q15 >> 7 = Q4 wk1 += ik->b00; // Q4 + 20Q4 // wk1 >>= 4; // 28Q4 -> 24Q0 - ret = (QMP6988_S32_t) wk1; + ret = (int32_t) wk1; return ret; } @@ -274,7 +247,7 @@ void QMP6988Component::set_power_mode_(uint8_t power_mode) { delay(10); } -void QMP6988Component::write_filter_(unsigned char filter) { +void QMP6988Component::write_filter_(QMP6988IIRFilter filter) { uint8_t data; data = (filter & 0x03); @@ -282,7 +255,7 @@ void QMP6988Component::write_filter_(unsigned char filter) { delay(10); } -void QMP6988Component::write_oversampling_pressure_(unsigned char oversampling_p) { +void QMP6988Component::write_oversampling_pressure_(QMP6988Oversampling oversampling_p) { uint8_t data; this->read_register(QMP6988_CTRLMEAS_REG, &data, 1); @@ -292,7 +265,7 @@ void QMP6988Component::write_oversampling_pressure_(unsigned char oversampling_p delay(10); } -void QMP6988Component::write_oversampling_temperature_(unsigned char oversampling_t) { +void QMP6988Component::write_oversampling_temperature_(QMP6988Oversampling oversampling_t) { uint8_t data; this->read_register(QMP6988_CTRLMEAS_REG, &data, 1); @@ -302,16 +275,6 @@ void QMP6988Component::write_oversampling_temperature_(unsigned char oversamplin delay(10); } -void QMP6988Component::set_temperature_oversampling(QMP6988Oversampling oversampling_t) { - this->temperature_oversampling_ = oversampling_t; -} - -void QMP6988Component::set_pressure_oversampling(QMP6988Oversampling oversampling_p) { - this->pressure_oversampling_ = oversampling_p; -} - -void QMP6988Component::set_iir_filter(QMP6988IIRFilter iirfilter) { this->iir_filter_ = iirfilter; } - void QMP6988Component::calculate_altitude_(float pressure, float temp) { float altitude; altitude = (pow((101325 / pressure), 1 / 5.257) - 1) * (temp + 273.15) / 0.0065; @@ -320,10 +283,10 @@ void QMP6988Component::calculate_altitude_(float pressure, float temp) { void QMP6988Component::calculate_pressure_() { uint8_t err = 0; - QMP6988_U32_t p_read, t_read; - QMP6988_S32_t p_raw, t_raw; + uint32_t p_read, t_read; + int32_t p_raw, t_raw; uint8_t a_data_uint8_tr[6] = {0}; - QMP6988_S32_t t_int, p_int; + int32_t t_int, p_int; this->qmp6988_data_.temperature = 0; this->qmp6988_data_.pressure = 0; @@ -332,13 +295,11 @@ void QMP6988Component::calculate_pressure_() { ESP_LOGE(TAG, "Error reading raw pressure/temp values"); return; } - p_read = (QMP6988_U32_t) ((((QMP6988_U32_t) (a_data_uint8_tr[0])) << SHIFT_LEFT_16_POSITION) | - (((QMP6988_U16_t) (a_data_uint8_tr[1])) << SHIFT_LEFT_8_POSITION) | (a_data_uint8_tr[2])); - p_raw = (QMP6988_S32_t) (p_read - SUBTRACTOR); + p_read = encode_uint24(a_data_uint8_tr[0], a_data_uint8_tr[1], a_data_uint8_tr[2]); + p_raw = (int32_t) (p_read - SUBTRACTOR); - t_read = (QMP6988_U32_t) ((((QMP6988_U32_t) (a_data_uint8_tr[3])) << SHIFT_LEFT_16_POSITION) | - (((QMP6988_U16_t) (a_data_uint8_tr[4])) << SHIFT_LEFT_8_POSITION) | (a_data_uint8_tr[5])); - t_raw = (QMP6988_S32_t) (t_read - SUBTRACTOR); + t_read = encode_uint24(a_data_uint8_tr[3], a_data_uint8_tr[4], a_data_uint8_tr[5]); + t_raw = (int32_t) (t_read - SUBTRACTOR); t_int = this->get_compensated_temperature_(&(qmp6988_data_.ik), t_raw); p_int = this->get_compensated_pressure_(&(qmp6988_data_.ik), p_raw, t_int); @@ -348,10 +309,9 @@ void QMP6988Component::calculate_pressure_() { } void QMP6988Component::setup() { - bool ret; - ret = this->device_check_(); - if (!ret) { - ESP_LOGCONFIG(TAG, "Setup failed - device not found"); + if (!this->device_check_()) { + this->mark_failed(ESP_LOG_MSG_COMM_FAIL); + return; } this->software_reset_(); @@ -365,9 +325,6 @@ void QMP6988Component::setup() { void QMP6988Component::dump_config() { ESP_LOGCONFIG(TAG, "QMP6988:"); LOG_I2C_DEVICE(this); - if (this->is_failed()) { - ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); - } LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); @@ -377,8 +334,6 @@ void QMP6988Component::dump_config() { ESP_LOGCONFIG(TAG, " IIR Filter: %s", iir_filter_to_str(this->iir_filter_)); } -float QMP6988Component::get_setup_priority() const { return setup_priority::DATA; } - void QMP6988Component::update() { this->calculate_pressure_(); float pressurehectopascals = this->qmp6988_data_.pressure / 100; diff --git a/esphome/components/qmp6988/qmp6988.h b/esphome/components/qmp6988/qmp6988.h index 61b46a4189..5b0f80c77e 100644 --- a/esphome/components/qmp6988/qmp6988.h +++ b/esphome/components/qmp6988/qmp6988.h @@ -1,24 +1,17 @@ #pragma once +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" namespace esphome { namespace qmp6988 { -#define QMP6988_U16_t unsigned short -#define QMP6988_S16_t short -#define QMP6988_U32_t unsigned int -#define QMP6988_S32_t int -#define QMP6988_U64_t unsigned long long -#define QMP6988_S64_t long long - /* oversampling */ -enum QMP6988Oversampling { +enum QMP6988Oversampling : uint8_t { QMP6988_OVERSAMPLING_SKIPPED = 0x00, QMP6988_OVERSAMPLING_1X = 0x01, QMP6988_OVERSAMPLING_2X = 0x02, @@ -30,7 +23,7 @@ enum QMP6988Oversampling { }; /* filter */ -enum QMP6988IIRFilter { +enum QMP6988IIRFilter : uint8_t { QMP6988_IIR_FILTER_OFF = 0x00, QMP6988_IIR_FILTER_2X = 0x01, QMP6988_IIR_FILTER_4X = 0x02, @@ -40,18 +33,18 @@ enum QMP6988IIRFilter { }; using qmp6988_cali_data_t = struct Qmp6988CaliData { - QMP6988_S32_t COE_a0; - QMP6988_S16_t COE_a1; - QMP6988_S16_t COE_a2; - QMP6988_S32_t COE_b00; - QMP6988_S16_t COE_bt1; - QMP6988_S16_t COE_bt2; - QMP6988_S16_t COE_bp1; - QMP6988_S16_t COE_b11; - QMP6988_S16_t COE_bp2; - QMP6988_S16_t COE_b12; - QMP6988_S16_t COE_b21; - QMP6988_S16_t COE_bp3; + int32_t COE_a0; + int16_t COE_a1; + int16_t COE_a2; + int32_t COE_b00; + int16_t COE_bt1; + int16_t COE_bt2; + int16_t COE_bp1; + int16_t COE_b11; + int16_t COE_bp2; + int16_t COE_b12; + int16_t COE_b21; + int16_t COE_bp3; }; using qmp6988_fk_data_t = struct Qmp6988FkData { @@ -60,9 +53,9 @@ using qmp6988_fk_data_t = struct Qmp6988FkData { }; using qmp6988_ik_data_t = struct Qmp6988IkData { - QMP6988_S32_t a0, b00; - QMP6988_S32_t a1, a2; - QMP6988_S64_t bt1, bt2, bp1, b11, bp2, b12, b21, bp3; + int32_t a0, b00; + int32_t a1, a2; + int64_t bt1, bt2, bp1, b11, bp2, b12, b21, bp3; }; using qmp6988_data_t = struct Qmp6988Data { @@ -77,17 +70,18 @@ using qmp6988_data_t = struct Qmp6988Data { class QMP6988Component : public PollingComponent, public i2c::I2CDevice { public: - void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } - void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; } + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; } + void set_pressure_sensor(sensor::Sensor *pressure_sensor) { this->pressure_sensor_ = pressure_sensor; } void setup() override; void dump_config() override; - float get_setup_priority() const override; void update() override; - void set_iir_filter(QMP6988IIRFilter iirfilter); - void set_temperature_oversampling(QMP6988Oversampling oversampling_t); - void set_pressure_oversampling(QMP6988Oversampling oversampling_p); + void set_iir_filter(QMP6988IIRFilter iirfilter) { this->iir_filter_ = iirfilter; } + void set_temperature_oversampling(QMP6988Oversampling oversampling_t) { + this->temperature_oversampling_ = oversampling_t; + } + void set_pressure_oversampling(QMP6988Oversampling oversampling_p) { this->pressure_oversampling_ = oversampling_p; } protected: qmp6988_data_t qmp6988_data_; @@ -102,14 +96,14 @@ class QMP6988Component : public PollingComponent, public i2c::I2CDevice { bool get_calibration_data_(); bool device_check_(); void set_power_mode_(uint8_t power_mode); - void write_oversampling_temperature_(unsigned char oversampling_t); - void write_oversampling_pressure_(unsigned char oversampling_p); - void write_filter_(unsigned char filter); + void write_oversampling_temperature_(QMP6988Oversampling oversampling_t); + void write_oversampling_pressure_(QMP6988Oversampling oversampling_p); + void write_filter_(QMP6988IIRFilter filter); void calculate_pressure_(); void calculate_altitude_(float pressure, float temp); - QMP6988_S32_t get_compensated_pressure_(qmp6988_ik_data_t *ik, QMP6988_S32_t dp, QMP6988_S16_t tx); - QMP6988_S16_t get_compensated_temperature_(qmp6988_ik_data_t *ik, QMP6988_S32_t dt); + int32_t get_compensated_pressure_(qmp6988_ik_data_t *ik, int32_t dp, int16_t tx); + int16_t get_compensated_temperature_(qmp6988_ik_data_t *ik, int32_t dt); }; } // namespace qmp6988 diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 9aaeb9f9e8..f811fbf7c2 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -393,10 +393,13 @@ def icon(value): ) -def sub_device_id(value: str | None) -> core.ID: +def sub_device_id(value: str | None) -> core.ID | None: # Lazy import to avoid circular imports from esphome.core.config import Device + if not value: + return None + return use_id(Device)(value) diff --git a/esphome/core/entity_helpers.py b/esphome/core/entity_helpers.py index 107b9fd739..1ccc3e2683 100644 --- a/esphome/core/entity_helpers.py +++ b/esphome/core/entity_helpers.py @@ -77,8 +77,8 @@ async def setup_entity(var: MockObj, config: ConfigType, platform: str) -> None: """ # Get device info device_name: str | None = None - if CONF_DEVICE_ID in config: - device_id_obj: ID = config[CONF_DEVICE_ID] + device_id_obj: ID | None + if device_id_obj := config.get(CONF_DEVICE_ID): device: MockObj = await get_variable(device_id_obj) add(var.set_device(device)) # Get device name for object ID calculation @@ -199,8 +199,8 @@ def entity_duplicate_validator(platform: str) -> Callable[[ConfigType], ConfigTy # Get device name if entity is on a sub-device device_name = None device_id = "" # Empty string for main device - if CONF_DEVICE_ID in config: - device_id_obj = config[CONF_DEVICE_ID] + device_id_obj: ID | None + if device_id_obj := config.get(CONF_DEVICE_ID): device_name = device_id_obj.id # Use the device ID string directly for uniqueness device_id = device_id_obj.id diff --git a/requirements.txt b/requirements.txt index 9793336cf3..0675115c02 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,8 +11,8 @@ pyserial==3.5 platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.0.2 click==8.1.7 -esphome-dashboard==20250514.0 -aioesphomeapi==38.2.1 +esphome-dashboard==20250814.0 +aioesphomeapi==39.0.0 zeroconf==0.147.0 puremagic==1.30 ruamel.yaml==0.18.14 # dashboard_import diff --git a/script/helpers.py b/script/helpers.py index b346f3a461..2c2f44a513 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -139,9 +139,24 @@ def _get_changed_files_github_actions() -> list[str] | None: if event_name == "pull_request": pr_number = _get_pr_number_from_github_env() if pr_number: - # Use GitHub CLI to get changed files directly + # Try gh pr diff first (faster for small PRs) cmd = ["gh", "pr", "diff", pr_number, "--name-only"] - return _get_changed_files_from_command(cmd) + try: + return _get_changed_files_from_command(cmd) + except Exception as e: + # If it fails due to the 300 file limit, use the API method + if "maximum" in str(e) and "files" in str(e): + cmd = [ + "gh", + "api", + f"repos/esphome/esphome/pulls/{pr_number}/files", + "--paginate", + "--jq", + ".[].filename", + ] + return _get_changed_files_from_command(cmd) + # Re-raise for other errors + raise # For pushes (including squash-and-merge) elif event_name == "push": diff --git a/tests/integration/fixtures/areas_and_devices.yaml b/tests/integration/fixtures/areas_and_devices.yaml index 12ab070e55..08b02e6e1e 100644 --- a/tests/integration/fixtures/areas_and_devices.yaml +++ b/tests/integration/fixtures/areas_and_devices.yaml @@ -55,6 +55,12 @@ sensor: lambda: return 4.0; update_interval: 0.1s + - platform: template + name: Living Room Sensor + device_id: "" + lambda: return 5.0; + update_interval: 0.1s + # Switches with the same name on different devices to test device_id lookup switch: # Switch with no device_id (defaults to 0) @@ -96,3 +102,23 @@ switch: - logger.log: "Turning on Test Switch on Motion Detector" turn_off_action: - logger.log: "Turning off Test Switch on Motion Detector" + + - platform: template + name: Living Room Blank Switch + device_id: "" + id: test_switch_blank_living_room + optimistic: true + turn_on_action: + - logger.log: "Turning on Living Room Blank Switch" + turn_off_action: + - logger.log: "Turning off Living Room Blank Switch" + + - platform: template + name: Living Room None Switch + device_id: + id: test_switch_none_living_room + optimistic: true + turn_on_action: + - logger.log: "Turning on Living Room None Switch" + turn_off_action: + - logger.log: "Turning off Living Room None Switch" diff --git a/tests/integration/test_areas_and_devices.py b/tests/integration/test_areas_and_devices.py index 1af16c87e8..93326de0a9 100644 --- a/tests/integration/test_areas_and_devices.py +++ b/tests/integration/test_areas_and_devices.py @@ -132,6 +132,7 @@ async def test_areas_and_devices( "Temperature Sensor Reading": temp_sensor.device_id, "Motion Detector Status": motion_detector.device_id, "Smart Switch Power": smart_switch.device_id, + "Living Room Sensor": 0, # Main device } for entity in sensor_entities: @@ -160,6 +161,18 @@ async def test_areas_and_devices( "Should have a switch with device_id 0 (main device)" ) + # Verify extra switches with blank and none device_id are correctly available + extra_switches = [ + e for e in switch_entities if e.name.startswith("Living Room") + ] + assert len(extra_switches) == 2, ( + f"Expected 2 extra switches for Living Room, got {len(extra_switches)}" + ) + extra_switch_device_ids = [e.device_id for e in extra_switches] + assert all(d == 0 for d in extra_switch_device_ids), ( + "All extra switches should have device_id 0 (main device)" + ) + # Wait for initial states to be received for all switches await asyncio.wait_for(initial_states_future, timeout=2.0) diff --git a/tests/script/test_helpers.py b/tests/script/test_helpers.py index 9730efd366..63f1f0e600 100644 --- a/tests/script/test_helpers.py +++ b/tests/script/test_helpers.py @@ -183,6 +183,61 @@ def test_get_changed_files_github_actions_pull_request( assert result == expected_files +def test_get_changed_files_github_actions_pull_request_large_pr( + monkeypatch: MonkeyPatch, +) -> None: + """Test _get_changed_files_github_actions fallback for PRs with >300 files.""" + monkeypatch.setenv("GITHUB_EVENT_NAME", "pull_request") + + expected_files = ["file1.py", "file2.cpp"] + + with ( + patch("helpers._get_pr_number_from_github_env", return_value="10214"), + patch("helpers._get_changed_files_from_command") as mock_get, + ): + # First call fails with too many files error, second succeeds with API method + mock_get.side_effect = [ + Exception("Sorry, the diff exceeded the maximum number of files (300)"), + expected_files, + ] + + result = _get_changed_files_github_actions() + + assert mock_get.call_count == 2 + mock_get.assert_any_call(["gh", "pr", "diff", "10214", "--name-only"]) + mock_get.assert_any_call( + [ + "gh", + "api", + "repos/esphome/esphome/pulls/10214/files", + "--paginate", + "--jq", + ".[].filename", + ] + ) + assert result == expected_files + + +def test_get_changed_files_github_actions_pull_request_other_error( + monkeypatch: MonkeyPatch, +) -> None: + """Test _get_changed_files_github_actions re-raises non-file-limit errors.""" + monkeypatch.setenv("GITHUB_EVENT_NAME", "pull_request") + + with ( + patch("helpers._get_pr_number_from_github_env", return_value="1234"), + patch("helpers._get_changed_files_from_command") as mock_get, + ): + # Error that is not about file limit + mock_get.side_effect = Exception("Command failed: authentication required") + + with pytest.raises(Exception, match="authentication required"): + _get_changed_files_github_actions() + + # Should only be called once (no retry with API) + mock_get.assert_called_once_with(["gh", "pr", "diff", "1234", "--name-only"]) + + def test_get_changed_files_github_actions_pull_request_no_pr_number( monkeypatch: MonkeyPatch, ) -> None: diff --git a/tests/unit_tests/core/test_entity_helpers.py b/tests/unit_tests/core/test_entity_helpers.py index 2157bc20a9..db99243a1a 100644 --- a/tests/unit_tests/core/test_entity_helpers.py +++ b/tests/unit_tests/core/test_entity_helpers.py @@ -689,3 +689,19 @@ def test_entity_duplicate_validator_internal_entities() -> None: Invalid, match=r"Duplicate sensor entity with name 'Temperature' found" ): validator(config4) + + +def test_empty_or_null_device_id_on_entity() -> None: + """Test that empty or null device IDs are handled correctly.""" + # Create validator for sensor platform + validator = entity_duplicate_validator("sensor") + + # Entity with empty device_id should pass + config1 = {CONF_NAME: "Battery", CONF_DEVICE_ID: ""} + validated1 = validator(config1) + assert validated1 == config1 + + # Entity with None device_id should pass + config2 = {CONF_NAME: "Temperature", CONF_DEVICE_ID: None} + validated2 = validator(config2) + assert validated2 == config2