diff --git a/Doxyfile b/Doxyfile index dc785e3a8f..bcaba5d42e 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.8.0b3 +PROJECT_NUMBER = 2025.8.0b4 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a 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/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 diff --git a/esphome/const.py b/esphome/const.py index bf0f2eaf8a..6cfff8801d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.8.0b3" +__version__ = "2025.8.0b4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = (