Skip to content

set_retry Deprecated: Use set_timeout or set_interval Instead

The set_retry, cancel_retry, and RetryResult APIs are deprecated. They will be removed in ESPHome 2026.8.0. Use set_timeout or set_interval instead.

This is a breaking change for external components in ESPHome 2026.2.0 and later.

Background

PR #13845: Deprecate set_retry, cancel_retry, and RetryResult

The set_retry API has several problems that make it unsuitable for ESPHome's embedded environment:

  1. Hidden heap allocation. Every set_retry call does a std::make_shared<RetryArgs>(), allocating on the heap. This is invisible to component authors and contributes to fragmentation on devices that run for months.

  2. Wasted flash and RAM. The retry machinery — RetryArgs struct, retry_handler(), set_retry_common_(), multiple overloads, and the heavier std::function<RetryResult(uint8_t)> template instantiation — is compiled into every firmware whether or not any component uses it.

  3. Confusing immediate-first-execution semantics. set_retry executes the callback immediately (delay=0) before waiting initial_wait_time for subsequent attempts. Callers expected it to wait first, like set_interval does.

  4. The abstraction didn't match real usage. All internal callers were using set_retry as a fixed-interval poll (backoff factor 1.0, always returning RETRY). These are trivially served by set_interval + counter or chained set_timeout.

All internal usage has been removed in companion PRs: #13841 (lps22), #13842 (ms8607), #13843 (speaker), #13844 (esp32_hosted).

What's Changing

The entire set_retry / cancel_retry / RetryResult API is marked ESPDEPRECATED. Using any of these will produce compiler warnings now. The API will be removed in 2026.8.0.

// All of these are deprecated:
this->set_retry("name", 100, 5, [](uint8_t) { return RetryResult::RETRY; });
this->set_retry(100, 5, [](uint8_t) { return RetryResult::RETRY; });
this->cancel_retry("name");
RetryResult result = RetryResult::DONE;

Who This Affects

External components that call set_retry or cancel_retry, or use RetryResult.

Standard YAML configurations are not affected.

Migration Guide

Pattern 1: Fixed-interval polling (most common)

The most common set_retry usage is polling at a fixed interval until a condition is met. Replace with set_interval and a counter:

// Before
void MyComponent::update() {
  this->trigger_measurement();
  this->set_retry("read", 5, 10, [this](uint8_t remaining) {
    if (this->data_ready()) {
      this->read_data();
      return RetryResult::DONE;
    }
    return RetryResult::RETRY;
  });
}

// After
static constexpr uint32_t INTERVAL_READ = 0;  // numeric ID

void MyComponent::update() {
  this->trigger_measurement();
  this->read_attempts_remaining_ = 10;
  this->set_interval(INTERVAL_READ, 5, [this]() {
    if (this->data_ready()) {
      this->cancel_interval(INTERVAL_READ);
      this->read_data();
    } else if (--this->read_attempts_remaining_ == 0) {
      this->cancel_interval(INTERVAL_READ);
    }
  });
}

Note: set_interval waits before the first execution, unlike set_retry which fired immediately. This is usually the desired behavior — it gives the hardware time to complete the operation before the first read attempt.

Pattern 2: Retry with backoff

For retries with increasing delays, use chained set_timeout:

// Before
this->set_retry("reset", 5, 3, [this](uint8_t remaining) {
  if (this->try_reset()) {
    return RetryResult::DONE;
  }
  return RetryResult::RETRY;
}, 5.0f);  // backoff factor

// After
void MyComponent::setup() {
  this->reset_attempts_remaining_ = 3;
  this->reset_interval_ = 5;
  this->try_reset_();
}

void MyComponent::try_reset_() {
  if (this->try_reset()) {
    // Success
    return;
  }
  if (--this->reset_attempts_remaining_ > 0) {
    uint32_t delay = this->reset_interval_;
    this->reset_interval_ *= 5;  // backoff factor
    this->set_timeout("reset", delay, [this]() { this->try_reset_(); });
  } else {
    this->mark_failed();
  }
}

Pattern 3: Wait for state transition

For waiting until something reaches a target state:

// Before
this->set_retry("wait_stop", 50, 3, [this](uint8_t remaining) {
  if (this->state_ == STATE_STOPPED) {
    this->on_stopped();
    return RetryResult::DONE;
  }
  return RetryResult::RETRY;
});

// After
this->wait_attempts_remaining_ = 3;
this->set_interval("wait_stop", 50, [this]() {
  if (this->state_ == STATE_STOPPED) {
    this->cancel_interval("wait_stop");
    this->on_stopped();
  } else if (--this->wait_attempts_remaining_ == 0) {
    this->cancel_interval("wait_stop");
  }
});

Supporting Multiple ESPHome Versions

No version guard is needed. set_interval and set_timeout are available in all ESPHome versions, so migrated code works everywhere.

Timeline

  • ESPHome 2026.2.0 (February 2026): Deprecation warnings active
  • ESPHome 2026.8.0 (August 2026): set_retry, cancel_retry, and RetryResult removed

Finding Code That Needs Updates

# Find set_retry usage
grep -rn 'set_retry' your_component/
grep -rn 'cancel_retry' your_component/
grep -rn 'RetryResult' your_component/

Questions?

If you have questions about migrating your external component, please ask in:

Comments

Feel free to leave a comment here to discuss this post wth others. You can ask questions, share your experience, or suggest improvements. If you have a question about a specific feature or issue, please consider using the ESPHome Discord. Stick to English and follow ESPHome's code of conduct. These comments exist on a discussion on GitHub, so you can also comment there directly if you prefer.