[sprinkler] Fix millis overflow and underflow bugs#14299
[sprinkler] Fix millis overflow and underflow bugs#14299swoboda1337 merged 2 commits intoesphome:devfrom
Conversation
|
👋 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 1 file(s) that were modified. Thanks for your time! 🙏 |
|
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#14299
components: [sprinkler]
refresh: 1h(Added by the PR bot) |
There was a problem hiding this comment.
Pull request overview
This PR aims to make SprinklerValveOperator::time_remaining() safe across millis() wrap-around by switching from an absolute “deadline” comparison to elapsed-time subtraction.
Changes:
- Reworks
time_remaining()to computeelapsedsincestart_millis_and compare againststart_delay_ + run_duration_. - Returns remaining seconds based on
total_duration - elapsedrather thancompleted_millis - millis().
df1c220 to
7558e63
Compare
Memory Impact AnalysisComponents:
📊 Component Memory Breakdown
🔍 Symbol-Level Changes (click to expand)Changed Symbols
This analysis runs automatically when components change. Memory usage is measured from a representative test configuration. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #14299 +/- ##
=======================================
Coverage 74.28% 74.28%
=======================================
Files 55 55
Lines 11595 11595
Branches 1583 1583
=======================================
Hits 8613 8613
Misses 2578 2578
Partials 404 404 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
7558e63 to
03f69c2
Compare
4f56715 to
d0a21f3
Compare
Switch SprinklerValveOperator from 32-bit millis() to 64-bit App.scheduler.millis_64() which matches the existing uint64_t start_millis_ and stop_millis_ fields. This fixes several issues: - loop(): remove broken millis rollover bailout that would stop the valve mid-run after ~49.7 days. The 32-bit now could never reach the 64-bit deadline, so the else branch would always trigger after wrap. With millis_64() the comparisons work correctly. - start()/stop(): store 64-bit timestamps to match field types. - time_remaining(): use 64-bit arithmetic instead of truncating cast hack. Previously the uint64_t start_millis_ was cast to uint32_t which loses high bits after wrap. Also fix unsigned underflow in total_cycle_time_enabled_incomplete_valves() where incomplete_valve_count-- would wrap to UINT32_MAX when both incomplete and enabled counts are zero. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Made-with: Cursor
a7cfc84 to
b430f55
Compare
|
Thanks |
What does this implement/fix?
Fix millis overflow handling in
SprinklerValveOperatorusing wrapping 32-bit subtraction instead of absolute deadline comparisons. Also fix several pre-existing bugs found during review.Approach
Rather than switching to 64-bit
millis_64()(which has performance overhead in the hotloop()path — called ~7100 times/min), use the standard embedded pattern of wrapping subtraction:(now - start) > delayinstead ofnow > (start + delay). This correctly handles the 49.7-daymillis()rollover with zero additional cost, using the already-cachedApp.get_loop_component_start_time().The
start_millis_andstop_millis_fields are changed fromuint64_ttooptional<uint32_t>to eliminate the use of0as a sentinel value (which is a validmillis()timestamp that occurs every 49.7 days).Bugs fixed:
loop()— valve stops mid-run after millis wrap:The code compared
uint32_t nowagainstuint64_t start_millis_deadline. After wrap,now(promoted to 64-bit) could never reach the 64-bit deadline, so it fell into theelsebranch which calledstop()with a TODO comment: "perhaps millis() rolled over...or something else is horribly wrong!". With wrapping subtraction, the comparisons work correctly and the bailout is removed.time_remaining()— returns ~49.7 days remaining after wrap:The deadline
start_millis_ + delay + durationexceedsUINT32_MAXbutmillis()can never reach it as a 32-bit value, so the valve appears stuck running forever. Now uses elapsed-time subtraction which handles wrap correctly.time_remaining()— unsigned underflow when stopped during start delay:Pre-existing bug: if a valve was stopped during the
start_delay_phase (before the run began),run_duration_ - (stop - start)would underflow since the elapsed time could exceedrun_duration_. Now correctly detects this case and returns the full run duration.time_remaining()— millis zero sentinel unreliable:Pre-existing bug:
start_millis_ == 0was used to mean "not started", but0is a validmillis()value. Changed tooptional<uint32_t>with.has_value()checks, matching the pattern used in[pn532] Replace millis zero sentinel with optional(#14295).total_cycle_time_enabled_incomplete_valves()— unsigned underflow:incomplete_valve_count--wraps toUINT32_MAXwhen both incomplete and enabled counts are zero, producing a bogus cycle time.set_repeat()— unchecked optional dereference:Pre-existing bug:
repeat.value()called withouthas_value()check. Changed tovalue_or(0)to match the usage two lines below in the same function.queue_valve()— unchecked optional dereference:Pre-existing bug:
run_duration.value()called withouthas_value()check. Changed tovalue_or(0)to match the log statement in the same function.Types of changes
Related issue or feature (if applicable):
N/A
Pull request in esphome-docs with documentation (if applicable):
N/A
Test Environment
Example entry for
config.yaml:# No config changesChecklist:
tests/folder).