diff --git a/esphome/components/daikin_madoka/daikin_madoka.cpp b/esphome/components/daikin_madoka/daikin_madoka.cpp index 283fe0a96ac7..5b198162b2a9 100644 --- a/esphome/components/daikin_madoka/daikin_madoka.cpp +++ b/esphome/components/daikin_madoka/daikin_madoka.cpp @@ -20,6 +20,12 @@ static const uint16_t CMD_GET_FAN_SPEED = 0x0050; static const uint16_t CMD_SET_FAN_SPEED = 0x4050; static const uint16_t CMD_GET_SENSOR_INFORMATION = 0x0110; +// Status retry parameters: if the BRC1H reports a status that disagrees with +// the last HA-issued status within this window, re-send SET_SETTING_STATUS up +// to MAX_STATUS_RETRIES times before accepting the readback as truth. +static const uint32_t STATUS_RETRY_WINDOW_MS = 30000; +static const uint8_t MAX_STATUS_RETRIES = 2; + void DaikinMadoka::dump_config() { LOG_CLIMATE(TAG, "Daikin Madoka Climate Controller", this); } void DaikinMadoka::setup() { this->receive_semaphore_ = xSemaphoreCreateMutex(); } @@ -81,6 +87,9 @@ void DaikinMadoka::control(const ClimateCall &call) { this->query_(CMD_SET_OPERATION_MODE, std::vector{0x20, 0x01, (uint8_t) mode_out}, 600); } this->query_(CMD_SET_SETTING_STATUS, std::vector{0x20, 0x01, (uint8_t) status_out}, 200); + this->last_set_status_byte_ = static_cast(status_out); + this->last_set_ms_ = millis(); + this->status_retry_count_ = 0; } std::vector temp_setpoint_args; if (call.get_target_temperature_high().has_value()) { @@ -321,6 +330,25 @@ void DaikinMadoka::parse_cb_(std::vector msg) { } i += len; } + // Status retry guard — see header comment on last_set_status_byte_. + if (this->last_set_ms_ > 0 && + (millis() - this->last_set_ms_) < STATUS_RETRY_WINDOW_MS && + ((uint8_t) (this->cur_status_.status ? 1 : 0)) != this->last_set_status_byte_ && + this->status_retry_count_ < MAX_STATUS_RETRIES) { + this->status_retry_count_++; + ESP_LOGW(TAG, + "[%s] SETTING_STATUS readback mismatch (got=%d, expected=%d), retry %d/%d", + this->get_name().c_str(), + (int) (this->cur_status_.status ? 1 : 0), + (int) this->last_set_status_byte_, + (int) this->status_retry_count_, + (int) MAX_STATUS_RETRIES); + this->query_(CMD_SET_SETTING_STATUS, + std::vector{0x20, 0x01, this->last_set_status_byte_}, 200); + // Don't let the rest of parse_cb_ act on the stale readback — pretend the + // pulse acknowledged what we asked for; the next poll round will confirm. + this->cur_status_.status = (this->last_set_status_byte_ != 0); + } break; case CMD_GET_OPERATION_MODE: while (i < message_size) { diff --git a/esphome/components/daikin_madoka/daikin_madoka.h b/esphome/components/daikin_madoka/daikin_madoka.h index c9725276f434..d6cb1ed894ba 100644 --- a/esphome/components/daikin_madoka/daikin_madoka.h +++ b/esphome/components/daikin_madoka/daikin_madoka.h @@ -58,6 +58,15 @@ class DaikinMadoka : public climate::Climate, public esphome::ble_client::BLECli uint16_t wwr_handle_; SemaphoreHandle_t receive_semaphore_ = nullptr; Status cur_status_; + // Status retry guard. The BRC1H sometimes drops the SET_SETTING_STATUS chunk + // silently (typically when one ESP juggles two BRC1H pairs at once over BLE). + // The unit then stays physically off even though we just told it to turn on, + // and the next poll reports status=0 and flips climate::mode to OFF in HA. + // Remember the requested status for a short window and re-issue + // SET_SETTING_STATUS if the readback disagrees, up to a small cap. + uint8_t last_set_status_byte_{0}; + uint32_t last_set_ms_{0}; + uint8_t status_retry_count_{0}; std::vector> split_payload_(std::vector msg); std::vector prepare_message_(uint16_t cmd, std::vector args);