diff --git a/.github/workflows/needs-docs.yml b/.github/workflows/needs-docs.yml deleted file mode 100644 index 628b5cc5e3..0000000000 --- a/.github/workflows/needs-docs.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Needs Docs - -on: - pull_request: - types: [labeled, unlabeled] - -jobs: - check: - name: Check - runs-on: ubuntu-latest - steps: - - name: Check for needs-docs label - uses: actions/github-script@v7.0.1 - with: - script: | - const { data: labels } = await github.rest.issues.listLabelsOnIssue({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number - }); - const needsDocs = labels.find(label => label.name === 'needs-docs'); - if (needsDocs) { - core.setFailed('Pull request needs docs'); - } diff --git a/.github/workflows/status-check-labels.yml b/.github/workflows/status-check-labels.yml new file mode 100644 index 0000000000..157f60f3a1 --- /dev/null +++ b/.github/workflows/status-check-labels.yml @@ -0,0 +1,30 @@ +name: Status check labels + +on: + pull_request: + types: [labeled, unlabeled] + +jobs: + check: + name: Check ${{ matrix.label }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + label: + - needs-docs + - merge-after-release + steps: + - name: Check for ${{ matrix.label }} label + uses: actions/github-script@v7.0.1 + with: + script: | + const { data: labels } = await github.rest.issues.listLabelsOnIssue({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number + }); + const hasLabel = labels.find(label => label.name === '${{ matrix.label }}'); + if (hasLabel) { + core.setFailed('Pull request cannot be merged, it is labeled as ${{ matrix.label }}'); + } diff --git a/esphome/components/atm90e32/atm90e32.cpp b/esphome/components/atm90e32/atm90e32.cpp index d5fee6ea04..634260b5e9 100644 --- a/esphome/components/atm90e32/atm90e32.cpp +++ b/esphome/components/atm90e32/atm90e32.cpp @@ -382,20 +382,15 @@ float ATM90E32Component::get_setup_priority() const { return setup_priority::IO; // R/C registers can conly be cleared after the LastSPIData register is updated (register 78H) // Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period // Default is 143FH (20ms, 63ms) -uint16_t ATM90E32Component::read16_transaction_(uint16_t a_register) { +uint16_t ATM90E32Component::read16_(uint16_t a_register) { + this->enable(); + delay_microseconds_safe(1); // min delay between CS low and first SCK is 200ns - 1us is plenty uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03); uint8_t addrl = (a_register & 0xFF); uint8_t data[4] = {addrh, addrl, 0x00, 0x00}; this->transfer_array(data, 4); uint16_t output = encode_uint16(data[2], data[3]); ESP_LOGVV(TAG, "read16_ 0x%04" PRIX16 " output 0x%04" PRIX16, a_register, output); - return output; -} - -uint16_t ATM90E32Component::read16_(uint16_t a_register) { - this->enable(); - delay_microseconds_safe(1); // min delay between CS low and first SCK is 200ns - 1us is plenty - uint16_t output = this->read16_transaction_(a_register); delay_microseconds_safe(1); // allow the last clock to propagate before releasing CS this->disable(); delay_microseconds_safe(1); // meet minimum CS high time before next transaction @@ -403,14 +398,8 @@ uint16_t ATM90E32Component::read16_(uint16_t a_register) { } int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) { - this->enable(); - delay_microseconds_safe(1); - const uint16_t val_h = this->read16_transaction_(addr_h); - delay_microseconds_safe(1); - const uint16_t val_l = this->read16_transaction_(addr_l); - delay_microseconds_safe(1); - this->disable(); - delay_microseconds_safe(1); + const uint16_t val_h = this->read16_(addr_h); + const uint16_t val_l = this->read16_(addr_l); const int32_t val = (val_h << 16) | val_l; ESP_LOGVV(TAG, diff --git a/esphome/components/atm90e32/atm90e32.h b/esphome/components/atm90e32/atm90e32.h index afbd9cf941..938ce512ce 100644 --- a/esphome/components/atm90e32/atm90e32.h +++ b/esphome/components/atm90e32/atm90e32.h @@ -140,7 +140,6 @@ class ATM90E32Component : public PollingComponent, number::Number *ref_currents_[3]{nullptr, nullptr, nullptr}; #endif uint16_t read16_(uint16_t a_register); - uint16_t read16_transaction_(uint16_t a_register); int read32_(uint16_t addr_h, uint16_t addr_l); void write16_(uint16_t a_register, uint16_t val, bool validate = true); float get_local_phase_voltage_(uint8_t phase); diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index d2cbdeb984..540492f8c5 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -375,10 +375,19 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga switch (event) { case ESP_GATTC_DISCONNECT_EVT: { - this->reset_connection_(param->disconnect.reason); + // Don't reset connection yet - wait for CLOSE_EVT to ensure controller has freed resources + // This prevents race condition where we mark slot as free before controller cleanup is complete + ESP_LOGD(TAG, "[%d] [%s] Disconnect, reason=0x%02x", this->connection_index_, this->address_str_.c_str(), + param->disconnect.reason); + // Send disconnection notification but don't free the slot yet + this->proxy_->send_device_connection(this->address_, false, 0, param->disconnect.reason); break; } case ESP_GATTC_CLOSE_EVT: { + ESP_LOGD(TAG, "[%d] [%s] Close, reason=0x%02x, freeing slot", this->connection_index_, this->address_str_.c_str(), + param->close.reason); + // Now the GATT connection is fully closed and controller resources are freed + // Safe to mark the connection slot as available this->reset_connection_(param->close.reason); break; } diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index c219b8851a..ac236f4eb3 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -824,8 +824,9 @@ async def to_code(config): cg.set_cpp_standard("gnu++20") cg.add_build_flag("-DUSE_ESP32") cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) - cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}") - cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[config[CONF_VARIANT]]) + variant = config[CONF_VARIANT] + cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{variant}") + cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[variant]) cg.add_define(ThreadModel.MULTI_ATOMICS) cg.add_platformio_option("lib_ldf_mode", "off") @@ -859,6 +860,7 @@ async def to_code(config): cg.add_platformio_option( "platform_packages", ["espressif/toolchain-esp32ulp@2.35.0-20220830"] ) + add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True) add_idf_sdkconfig_option( f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True ) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index 133bd2947c..b348bc9920 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -764,7 +764,8 @@ void Nextion::process_nextion_commands_() { variable_name = to_process.substr(0, index); ++index; - text_value = to_process.substr(index); + // Get variable value without terminating NUL byte. Length check above ensures substr len >= 0. + text_value = to_process.substr(index, to_process_length - index - 1); ESP_LOGN(TAG, "Text sensor: %s='%s'", variable_name.c_str(), text_value.c_str()); diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp index 40405114a4..5751ad59f5 100644 --- a/esphome/components/pipsolar/pipsolar.cpp +++ b/esphome/components/pipsolar/pipsolar.cpp @@ -23,20 +23,18 @@ void Pipsolar::loop() { // Read message if (this->state_ == STATE_IDLE) { this->empty_uart_buffer_(); - switch (this->send_next_command_()) { - case 0: - // no command send (empty queue) time to poll - if (millis() - this->last_poll_ > this->update_interval_) { - this->send_next_poll_(); - this->last_poll_ = millis(); - } - return; - break; - case 1: - // command send - return; - break; + + if (this->send_next_command_()) { + // command sent + return; } + + if (this->send_next_poll_()) { + // poll sent + return; + } + + return; } if (this->state_ == STATE_COMMAND_COMPLETE) { if (this->check_incoming_length_(4)) { @@ -530,7 +528,7 @@ void Pipsolar::loop() { // '(00000000000000000000000000000000' // iterate over all available flag (as not all models have all flags, but at least in the same order) this->value_warnings_present_ = false; - this->value_faults_present_ = true; + this->value_faults_present_ = false; for (size_t i = 1; i < strlen(tmp); i++) { enabled = tmp[i] == '1'; @@ -708,6 +706,7 @@ void Pipsolar::loop() { return; } // crc ok + this->used_polling_commands_[this->last_polling_command_].needs_update = false; this->state_ = STATE_POLL_CHECKED; return; } else { @@ -788,7 +787,7 @@ uint8_t Pipsolar::check_incoming_crc_() { } // send next command used -uint8_t Pipsolar::send_next_command_() { +bool Pipsolar::send_next_command_() { uint16_t crc16; if (!this->command_queue_[this->command_queue_position_].empty()) { const char *command = this->command_queue_[this->command_queue_position_].c_str(); @@ -809,37 +808,43 @@ uint8_t Pipsolar::send_next_command_() { // end Byte this->write(0x0D); ESP_LOGD(TAG, "Sending command from queue: %s with length %d", command, length); - return 1; + return true; } - return 0; + return false; } -void Pipsolar::send_next_poll_() { +bool Pipsolar::send_next_poll_() { uint16_t crc16; - this->last_polling_command_ = (this->last_polling_command_ + 1) % 15; - if (this->used_polling_commands_[this->last_polling_command_].length == 0) { - this->last_polling_command_ = 0; + + for (uint8_t i = 0; i < POLLING_COMMANDS_MAX; i++) { + this->last_polling_command_ = (this->last_polling_command_ + 1) % POLLING_COMMANDS_MAX; + if (this->used_polling_commands_[this->last_polling_command_].length == 0) { + // not enabled + continue; + } + if (!this->used_polling_commands_[this->last_polling_command_].needs_update) { + // no update requested + continue; + } + this->state_ = STATE_POLL; + this->command_start_millis_ = millis(); + this->empty_uart_buffer_(); + this->read_pos_ = 0; + crc16 = this->pipsolar_crc_(this->used_polling_commands_[this->last_polling_command_].command, + this->used_polling_commands_[this->last_polling_command_].length); + this->write_array(this->used_polling_commands_[this->last_polling_command_].command, + this->used_polling_commands_[this->last_polling_command_].length); + // checksum + this->write(((uint8_t) ((crc16) >> 8))); // highbyte + this->write(((uint8_t) ((crc16) &0xff))); // lowbyte + // end Byte + this->write(0x0D); + ESP_LOGD(TAG, "Sending polling command : %s with length %d", + this->used_polling_commands_[this->last_polling_command_].command, + this->used_polling_commands_[this->last_polling_command_].length); + return true; } - if (this->used_polling_commands_[this->last_polling_command_].length == 0) { - // no command specified - return; - } - this->state_ = STATE_POLL; - this->command_start_millis_ = millis(); - this->empty_uart_buffer_(); - this->read_pos_ = 0; - crc16 = this->pipsolar_crc_(this->used_polling_commands_[this->last_polling_command_].command, - this->used_polling_commands_[this->last_polling_command_].length); - this->write_array(this->used_polling_commands_[this->last_polling_command_].command, - this->used_polling_commands_[this->last_polling_command_].length); - // checksum - this->write(((uint8_t) ((crc16) >> 8))); // highbyte - this->write(((uint8_t) ((crc16) &0xff))); // lowbyte - // end Byte - this->write(0x0D); - ESP_LOGD(TAG, "Sending polling command : %s with length %d", - this->used_polling_commands_[this->last_polling_command_].command, - this->used_polling_commands_[this->last_polling_command_].length); + return false; } void Pipsolar::queue_command_(const char *command, uint8_t length) { @@ -869,7 +874,13 @@ void Pipsolar::dump_config() { } } } -void Pipsolar::update() {} +void Pipsolar::update() { + for (auto &used_polling_command : this->used_polling_commands_) { + if (used_polling_command.length != 0) { + used_polling_command.needs_update = true; + } + } +} void Pipsolar::add_polling_command_(const char *command, ENUMPollingCommand polling_command) { for (auto &used_polling_command : this->used_polling_commands_) { @@ -891,6 +902,7 @@ void Pipsolar::add_polling_command_(const char *command, ENUMPollingCommand poll used_polling_command.errors = 0; used_polling_command.identifier = polling_command; used_polling_command.length = length - 1; + used_polling_command.needs_update = true; return; } } diff --git a/esphome/components/pipsolar/pipsolar.h b/esphome/components/pipsolar/pipsolar.h index 373911b2d7..77b18badb9 100644 --- a/esphome/components/pipsolar/pipsolar.h +++ b/esphome/components/pipsolar/pipsolar.h @@ -25,6 +25,7 @@ struct PollingCommand { uint8_t length = 0; uint8_t errors; ENUMPollingCommand identifier; + bool needs_update; }; #define PIPSOLAR_VALUED_ENTITY_(type, name, polling_command, value_type) \ @@ -189,14 +190,14 @@ class Pipsolar : public uart::UARTDevice, public PollingComponent { static const size_t PIPSOLAR_READ_BUFFER_LENGTH = 110; // maximum supported answer length static const size_t COMMAND_QUEUE_LENGTH = 10; static const size_t COMMAND_TIMEOUT = 5000; - uint32_t last_poll_ = 0; + static const size_t POLLING_COMMANDS_MAX = 15; void add_polling_command_(const char *command, ENUMPollingCommand polling_command); void empty_uart_buffer_(); uint8_t check_incoming_crc_(); uint8_t check_incoming_length_(uint8_t length); uint16_t pipsolar_crc_(uint8_t *msg, uint8_t len); - uint8_t send_next_command_(); - void send_next_poll_(); + bool send_next_command_(); + bool send_next_poll_(); void queue_command_(const char *command, uint8_t length); std::string command_queue_[COMMAND_QUEUE_LENGTH]; uint8_t command_queue_position_ = 0; @@ -216,7 +217,7 @@ class Pipsolar : public uart::UARTDevice, public PollingComponent { }; uint8_t last_polling_command_ = 0; - PollingCommand used_polling_commands_[15]; + PollingCommand used_polling_commands_[POLLING_COMMANDS_MAX]; }; } // namespace pipsolar