8000
Skip to content

[sprinkler] Fix scheduler deprecation warnings and heap churn with FixedVector - #13251

Merged
bdraco merged 1 commit into
devfrom
sprinkler_schedule_heap_churn_fix
Jan 15, 2026
Merged

[sprinkler] Fix scheduler deprecation warnings and heap churn with FixedVector#13251
bdraco merged 1 commit into
devfrom
sprinkler_schedule_heap_churn_fix

Conversation

@bdraco
@bdraco bdraco commented Jan 15, 2026
Copy link
Copy Markdown
Member

What does this implement/fix?

Fix deprecation warnings and eliminate hidden heap churn in sprinkler component by using const char* overload of set_timeout() and cancel_timeout() instead of the deprecated std::string overloads.

The timer names are stored in SprinklerTimer structs with const std::string name members. Since these are initialized once in the constructor and never modified, calling .c_str() on them is safe.

Key change: Migrated timer_ from std::vector<SprinklerTimer> to FixedVector<SprinklerTimer>.

While std::vector would have worked (the vector was only populated in the constructor), using FixedVector enforces this invariant at the type level. This prevents future refactoring from accidentally adding push_back() calls elsewhere that could invalidate the .c_str() pointers. The type system now guarantees the safety property we're relying on.

Before: Each set_timeout() and cancel_timeout() call caused heap allocation when the scheduler copied the string name.

After: Zero heap allocation - the scheduler stores pointers to the persistent member strings. FixedVector ensures these pointers remain valid.

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Developer breaking change (an API change that could break external components)
  • Code quality improvements to existing code or addition of tests
  • Other

Related issue or feature (if applicable):

  • fixes deprecation warning for set_timeout(const std::string&, ...) and cancel_timeout(const std::string&, ...) in sprinkler

Pull request in esphome-docs with documentation (if applicable):

  • N/A

Test Environment

  • ESP32
  • ESP32 IDF
  • ESP8266
  • RP2040
  • BK72xx
  • RTL87xx
  • LN882x
  • nRF52840

Example entry for config.yaml:

# Existing sprinkler configurations work without changes
switch:
  - platform: template
    id: pump_switch
    optimistic: true
  - platform: template
    id: valve_switch<
8000
/span>
    optimistic: true

sprinkler:
  - id: test_sprinkler
    main_switch: Test Sprinklers
    valves:
      - valve_switch: Valve 1
        pump_switch_id: pump_switch
        run_duration: 10s
        valve_switch_id: valve_switch

Checklist:

  • The code change is tested and works locally.
  • Tests have been added to verify that the new code works (under tests/ folder).

If user exposed functionality or configuration variables are added/changed:

@bdraco bdraco added this to the 2026.1.0b3 milestone Jan 15, 2026
Copilot AI review requested due to automatic review settings January 15, 2026 21:05
@bdraco
bdraco requested a review from kbx81 as a code owner January 15, 2026 21:05
@github-actions
Copy link
Copy Markdown
Contributor

To use the changes from this PR as an external component, add the following to your ESPHome configuration YAML file:

external_components:
  - source: github://pr#13251
    components: [sprinkler]
    refresh: 1h

(Added by the PR bot)

@github-actions
Copy link
Copy Markdown
Contributor

👋 Hi there! I've automatically requested reviews from codeowners based on the files changed in this PR.

@kbx81 - You've been requested to review this PR as codeowner(s) of 2 file(s) that were modified. Thanks for your time! 🙏

@codecov-commenter
codecov-commenter commented Jan 15, 2026
Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 73.79%. Comparing base (00cc9e4) to head (eff91f8).

Additional details and impacted files
@@           Coverage Diff           @@
##              dev   #13251   +/-   ##
=======================================
  Coverage   73.79%   73.79%           
=======================================
  Files          53       53           
  Lines       11329    11329           
  Branches     1538     1538           
=======================================
  Hits         8360     8360           
  Misses       2570     2570           
  Partials      399      399           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request fixes deprecation warnings in the sprinkler component by migrating from the deprecated std::string overloads of set_timeout() and cancel_timeout() to the const char* overloads, which eliminates hidden heap allocations in the scheduler.

Changes:

  • Migrated timer_ from std::vector<SprinklerTimer> to FixedVector<SprinklerTimer> to ensure the vector capacity never changes after initialization
  • Updated start_timer_() and cancel_timer_() to use .c_str() on timer names when calling scheduler methods
  • Added include for esphome/core/helpers.h to access FixedVector

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
esphome/components/sprinkler/sprinkler.h Added helpers.h include and changed timer_ from std::vector to FixedVector with explanatory comment
esphome/components/sprinkler/sprinkler.cpp Initialized FixedVector with capacity 2 in constructor, and updated scheduler calls to use .c_str() on timer names

@github-actions
Copy link
Copy Markdown
Contributor

Memory Impact Analysis

Components: sprinkler
Platform: esp8266-ard

Metric Target Branch This PR Change
RAM 28,700 bytes 28,700 bytes ➡️ +0 bytes (0.00%)
Flash 299,727 bytes 299,411 bytes 📉 ✅ -316 bytes (-0.11%)
📊 Component Memory Breakdown
Component Target Flash PR Flash Change
[esphome]sprinkler 13,955 bytes 13,717 bytes 📉 ✅ -238 bytes (-1.71%)
[esphome]core 8,613 bytes 8,561 bytes 📉 ✅ -52 bytes (-0.60%)
🔍 Symbol-Level Changes (click to expand)

Changed Symbols

Symbol Target Size PR Size Change
esphome::sprinkler::Sprinkler::Sprinkler(std::__cxx11::basic_string<char, std::char_traits,...esphome::sprinkler::Sprinkler::Sprinkler(std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&)
493 bytes 581 bytes 📈 +88 bytes (+17.85%)
esphome::sprinkler::Sprinkler::add_valve(esphome::sprinkler::SprinklerControllerSwitch*, esphome:...esphome::sprinkler::Sprinkler::add_valve(esphome::sprinkler::SprinklerControllerSwitch*, esphome::sprinkler::SprinklerControllerSwitch*)
391 bytes 387 bytes 📉 -4 bytes (-1.02%)
esphome::sprinkler::Sprinkler::cancel_timer_(esphome::sprinkler::SprinklerTimerIndex) 26 bytes 28 bytes 📈 +2 bytes (+7.69%)

New Symbols (top 15)

Symbol Size
esphome::FixedVectoresphome::sprinkler::SprinklerTimer::push_back(esphome::sprinkler::Sprinkler...esphome::FixedVectoresphome::sprinkler::SprinklerTimer::push_back(esphome::sprinkler::SprinklerTimer&&)
105 bytes
esphome::Scheduler::set_timeout(esphome::Component*, char const*, unsigned int, std::function<voi...esphome::Scheduler::set_timeout(esphome::Component*, char const*, unsigned int, std::function<void ()>)
85 bytes
esphome::Component::set_timeout(char const*, unsigned int, std::function<void ()>& 8000 amp;&) 58 bytes
esphome::Scheduler::cancel_timeout(esphome::Component*, char const*) 27 bytes
CSWTCH$561 20 bytes
esphome::Component::cancel_timeout(char const*) 13 bytes
CSWTCH$559 12 bytes

Removed Symbols (top 15)

Symbol Size
void std::vector<esphome::sprinkler::SprinklerTimer, std::allocator<esphome::sprinkler::Sprinkler...void std::vector<esphome::sprinkler::SprinklerTimer, std::allocatoresphome::sprinkler::SprinklerTimer >::_M_realloc_insertesphome::sprinkler::SprinklerTimer(__gnu_cxx::__normal_iterator<esphome::sprinkler::SprinklerTimer*, std::vector<esphome::sprinkler::SprinklerTimer, std::allocatoresphome::sprinkler::SprinklerTimer > >, esphome::sprinkler::SprinklerTimer&&)
199 bytes
esphome::Scheduler::set_timeout(esphome::Component*, std::__cxx11::basic_string<char, std::char_t...esphome::Scheduler::set_timeout(esphome::Component*, std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, unsigned int, std::function<void ()>)
91 bytes
esphome::sprinkler::SprinklerTimer* std::__uninitialized_copy::__uninit_copy<esphome::spri...esphome::sprinkler::SprinklerTimer* std::__uninitialized_copy::__uninit_copy<esphome::sprinkler::SprinklerTimer const*, esphome::sprinkler::SprinklerTimer*>(esphome::sprinkler::SprinklerTimer const*, esphome::sprinkler::SprinklerTimer const*, esphome::sprinkler::SprinklerTimer*)
74 bytes
esphome::sprinkler::SprinklerTimer::SprinklerTimer(esphome::sprinkler::SprinklerTimer&&) 73 bytes
esphome::Component::set_timeout(std::__cxx11::basic_string<char, std::char_traits, std::all...esphome::Component::set_timeout(std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, unsigned int, std::function<void ()>&&)
58 bytes
esphome::sprinkler::SprinklerTimer& std::vector<esphome::sprinkler::SprinklerTimer, std::allocato...esphome::sprinkler::SprinklerTimer& std::vector<esphome::sprinkler::SprinklerTimer, std::allocatoresphome::sprinkler::SprinklerTimer >::emplace_backesphome::sprinkler::SprinklerTimer(esphome::sprinkler::SprinklerTimer&&) [$isra$0]
57 bytes
esphome::Scheduler::cancel_timeout(esphome::Component*, std::__cxx11::basic_string<char, std::cha...esphome::Scheduler::cancel_timeout(esphome::Component*, std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&)
45 bytes
esphome::fnv1a_hash(std::__cxx11::basic_string<char, std::char_traits, std::allocator...esphome::fnv1a_hash(std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&) [$isra$0]
28 bytes
std::vector<esphome::sprinkler::SprinklerTimer, std::allocator<esphome::sprinkler::SprinklerTimer...std::vector<esphome::sprinkler::SprinklerTimer, std::allocatoresphome::sprinkler::SprinklerTimer >::_M_realloc_insertesphome::sprinkler::SprinklerTimer(__gnu_cxx::__normal_iterator<esphome::sprinkler::SprinklerTimer*, std::vector<esphome::sprinkler::SprinklerTimer, std::allocatoresphome::sprinkler::SprinklerTimer > >, esphome::sprinkler::SprinklerTimer&&)::exception_what
26 bytes
CSWTCH$570 20 bytes
esphome::Component::cancel_timeout(std::__cxx11::basic_string<char, std::char_traits, std::...esphome::Component::cancel_timeout(std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&)
13 bytes
CSWTCH$568 12 bytes

Note: This analysis measures static RAM and Flash usage only (compile-time allocation).
Dynamic memory (heap) cannot be measured automatically.
⚠️ You must test this PR on a real device to measure free heap and ensure no runtime memory issues.

This analysis runs automatically when components change. Memory usage is measured from a representative test configuration.

/// Valve control timers
std::vector<SprinklerTimer> timer_{};
/// Valve control timers - FixedVector enforces that this can never grow beyond init() size
FixedVector<SprinklerTimer> timer_;
@kbx81 kbx81 Jan 15, 2026
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could probably do the same for other_controllers_ below, as that size should always be known at compile time

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Err I guess StaticVector might be better for that case. haha

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about StaticVector first, but won't work because it can't handle moving std::string. Its intentionally missing the move support.

@kbx81 kbx81 left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks 🍻

@bdraco
bdraco commented Jan 15, 2026
Copy link
Copy Markdown
Member Author

thanks

@bdraco
bdraco merged commit 535c3eb into dev Jan 15, 2026
46 checks passed
@bdraco
bdraco deleted the sprinkler_schedule_heap_churn_fix branch January 15, 2026 21:32
@github-actions github-actions Bot locked and limited conversation to collaborators Jan 17, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

0