Skip to content

Damp latency-driven grid oscillation in active control (#473)#475

Merged
tomquist merged 3 commits into
developfrom
claude/washer-oscillation-fix
Jun 13, 2026
Merged

Damp latency-driven grid oscillation in active control (#473)#475
tomquist merged 3 commits into
developfrom
claude/washer-oscillation-fix

Conversation

@tomquist

@tomquist tomquist commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Problem

When the meter the controller acts on lags reality (a Home Assistant push sensor with measurement delay, a slow P1 dongle), active control hunts continuously instead of settling — the grid never holds zero. The gain‑1 grid‑following residual keeps reacting to a stale reading that doesn't yet reflect its last command, so it overshoots and limit‑cycles. This is the field report in #473, reproduced by the single_venus_washer evaluation scenario (added in #474).

Fix

Oscillation‑gated damping on the per‑battery residual (_damp_oscillation / damp_oscillation_, Python + ESPHome C++ in lockstep): an accumulating score tracks how often the residual reverses sign — the signature of hunting, not of a real load change — and bleeds the loop gain by up to osc_damp_max as the score rises. Two guards keep genuine reactions at full speed:

  • Magnitude threshold — a residual past osc_damp_threshold (450 W) is a real demand step (kettle, solar ramp), never damped.
  • Accumulation, not a one‑off — a single zero‑crossing (e.g. a solar ramp crossing discharge→charge once) barely moves the score; only sustained reversals engage the damper.

A clean load/solar step holds one sign, so the score stays ~0 and reaction speed is unchanged. Defaults osc_damp_max=0.8, osc_damp_alpha=0.15, osc_damp_decay=0.1, osc_damp_threshold=450, tuned on the steering‑eval suite; tune or disable via OSC_DAMP_MAX / OSC_DAMP_ALPHA / OSC_DAMP_DECAY / OSC_DAMP_THRESHOLD (osc_damp_* on ESPHome).

Results (steering‑eval, seed 1 — CI posts the full 3‑seed base‑vs‑head table)

scenario metric base → head
single_venus_washer grid_p2p_w 340 → 241 (−29%)
band_crossings/h 828 → 500 (−40%)
mean_abs_grid_w 68 → 43 (−37%)
battery_travel 121k → 62k (−49%)
mixed_venus_b2500/fair band_crossings/h 348 → 93 (−73%)
settle_mean_s 111 → 76 (−32%)
two_venus/eff band_crossings/h 152 → 41 (−73%)
settle_mean_s 25 → 15
mixed_cadence/eff settle_mean_s 72 → 52

Broad improvement across the matrix (settle, band‑crossings, grid swing, battery travel down; several overshoots down too). Honest trade‑offs: a few …/fair solar scenarios show a small settle/p2p rise (~+10–16%, e.g. mixed_cadence_solar/fair settle 56→65) where the damper mildly slows a transition; these are minor and partly seed noise. Tuning alpha trades washer‑damping strength against this — 0.15 was chosen as the balance point (0.25 damps the washer harder but regresses mixed_cadence/fair overshoot).

Tests

  • New TestDampOscillation unit tests (steady step not damped, sustained hunt damped, large residual bypasses, single reversal barely damps, disable switch).
  • The existing C++↔Python differential parity suite passes (both stacks run the same damper on the same residual stream).
  • ruff / mypy / pytest green.

Parity: simulator/eval is Python‑only, but the balancer change is mirrored in esphome/components/ct002/ (config, codegen, test hooks) per the parity rule.

Draft for review of the tuning trade‑off before finalizing.

https://claude.ai/code/session_01V6PWDgijfH7nmniL47A2qy


Generated by Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Fixed continuous grid oscillation with slow/laggy meter inputs by detecting hunting behavior and adaptively damping corrective commands.
  • New Features

    • Added configurable oscillation-damping tuning (max reduction, ramp-up alpha, decay, and magnitude threshold) with defaults and validation; settings exposed in the UI and external integration.
  • Tests

    • Added unit tests validating damping behavior across steady, hunting, threshold-bypass, single-reversal, and disabled scenarios.

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7ba3adc0-618f-4bf7-a847-344fa8051331

📥 Commits

Reviewing files that changed from the base of the PR and between 0653acd and 6392840.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • esphome/components/ct002/balancer.cpp
  • src/astrameter/ct002/balancer.py
  • src/astrameter/main.py
  • src/astrameter/web_config.py
  • tests/test_balancer.py
  • web/ts/schema.ts
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/astrameter/web_config.py
  • esphome/components/ct002/balancer.cpp
  • src/astrameter/main.py
  • tests/test_balancer.py
  • src/astrameter/ct002/balancer.py

Walkthrough

This PR adds oscillation-damping capability to the CT002 power meter load balancer to suppress continuous hunting caused by laggy meter readings. A new residual-sign-reversal detection algorithm scales down computed residuals when the system repeatedly changes direction, while bypassing damping for genuinely large load steps. Configuration parameters control damping intensity, decay rate, and threshold. The feature is implemented across ESPHome (C++) and Python reference balancer, with full test coverage.

Changes

Oscillation Damping Feature

Layer / File(s) Summary
Configuration schema and changelog
CHANGELOG.md, esphome/components/ct002/__init__.py, src/astrameter/web_config.py, src/astrameter/main.py, src/astrameter/ct002/ct002.py
New oscillation-damping configuration constants and schema fields (OSC_DAMP_MAX, OSC_DAMP_ALPHA, OSC_DAMP_DECAY, OSC_DAMP_THRESHOLD) are declared, read from CT config, threaded into CT002 constructor, and included in ESPHome codegen; changelog updated.
Data structure definitions
esphome/components/ct002/balancer.h, src/astrameter/ct002/balancer.py
BalancerConfig gains four oscillation-damping tuning fields and BalancerConsumerState gains osc_score and osc_last_sign; Python __post_init__ and C++ clamping enforce valid ranges.
Core oscillation damping algorithm
esphome/components/ct002/balancer.cpp, src/astrameter/ct002/balancer.py
damp_oscillation_() / _damp_oscillation() implement hunting detection via residual sign-reversal scoring with EMA-style decay and increment, threshold-based bypass logic, and residual scaling applied after residual sign-disagreement handling.
Parameter validation and state reset
esphome/components/ct002/balancer.cpp, esphome/components/ct002/test_hooks.cpp, src/astrameter/ct002/balancer.py
BalancerConfig::clamp() bounds osc parameters; reset_consumer clears per-consumer oscillation state; apply_cfg_ accepts new runtime keys.
Testing and web editor schema
tests/test_balancer.py, web/ts/schema.ts
TestDampOscillation suite validates damping behavior across scenarios; web TS schema gains fields for the new CT balancer options.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change—adding oscillation damping to address latency-driven grid oscillation in the active balancer control system, which is reflected across all modified files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/washer-oscillation-fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Steering evaluation (base vs head)

Lower is better for every metric. See src/astrameter/simulator/evaluation.py for definitions.

📊 Interactive grid-power charts (zoom / hover / toggle series) are in the self-contained steering-eval-report.html report — see the link below (it opens directly in the browser).

What do these metrics mean?
Metric Meaning
settle_mean_s Mean seconds after a load/PV step for grid power to return inside the ±25 W settle band and hold for 10 s (reaction speed).
settle_p95_s 95th-percentile settle time — the slow tail of reactions.
unsettled_events Number of disturbance events that never settled within the 10-minute measurement window.
overshoot_mean_w Mean overshoot (W): how far grid power swings past zero to the opposite sign after an event.
overshoot_max_w Worst-case overshoot (W) across all events.
band_crossings_per_h Sign flips per hour across the ±20 W hysteresis band — oscillation / hunting frequency.
grid_p2p_w Sustained peak-to-peak grid swing (95th - 5th percentile) over the whole run — oscillation amplitude. Non-zero whenever the loop keeps hunting, including continuous oscillation the step-response metrics (settle/overshoot) miss.
steady_rms_w RMS grid power (W) during steady state (excluding the 120 s after each event) — residual jitter when nothing is changing.
mean_abs_grid_w Mean absolute grid power (W) over the whole run — overall tracking accuracy.
avoidable_import_wh Energy imported from the grid (Wh) the battery could have supplied (it had charge and discharge headroom) — missed self-consumption.
avoidable_export_wh Energy exported to the grid (Wh) an AC-chargeable battery could have absorbed (it had room and charge headroom) — missed charging.
battery_travel_w_per_h Total absolute change in battery setpoints per hour (W/h) — control effort / actuator wear; lower is smoother.
mixed_cadence/eff — settle 71.9→52.5s, overshoot 352.8→327.2W, RMS 25.0→21.5W
Metric Base Head Δ
settle_mean_s 71.9 52.5 -27%
settle_p95_s 111.3 71.3 -36%
unsettled_events 0 0 =
overshoot_mean_w 161.2 164.8 +2%
overshoot_max_w 352.8 327.2 -7%
band_crossings_per_h 347.0 224.0 -35%
grid_p2p_w 131.0 105.7 -19%
steady_rms_w 25.0 21.5 -14%
mean_abs_grid_w 59.8 55.7 -7%
avoidable_import_wh 36.8 34.8 -5%
avoidable_export_wh 23.1 21.0 -9%
battery_travel_w_per_h 70799.0 45325.0 -36%
mixed_cadence/fair — settle 52.9→51.8s, overshoot 161.0→133.6W, RMS 15.1→15.2W
Metric Base Head Δ
settle_mean_s 52.9 51.8 -2%
settle_p95_s 76.2 75.7 -1%
unsettled_events 0 0 =
overshoot_mean_w 75.9 71.5 -6%
overshoot_max_w 161.0 133.6 -17%
band_crossings_per_h 114.0 95.0 -17%
grid_p2p_w 65.4 70.3 +7%
steady_rms_w 15.1 15.2 +1%
mean_abs_grid_w 44.4 45.7 +3%
avoidable_import_wh 28.7 30.2 +5%
avoidable_export_wh 15.7 15.5 -1%
battery_travel_w_per_h 35042.0 31535.0 -10%
mixed_cadence_solar/eff — settle 58.3→93.4s, overshoot 289.2→293.9W, RMS 24.4→28.1W
Metric Base Head Δ
settle_mean_s 58.3 93.4 +60%
settle_p95_s 123.0 277.3 +125%
unsettled_events 2 2 =
overshoot_mean_w 120.1 127.0 +6%
overshoot_max_w 289.2 293.9 +2%
band_crossings_per_h 166.0 160.67 -3%
grid_p2p_w 109.3 113.4 +4%
steady_rms_w 24.4 28.1 +15%
mean_abs_grid_w 58.8 61.0 +4%
avoidable_import_wh 48.3 49.8 +3%
avoidable_export_wh 39.5 41.2 +4%
battery_travel_w_per_h 43127.0 42973.0 -0%
mixed_cadence_solar/fair — settle 50.4→78.8s, overshoot 190.8→203.1W, RMS 19.4→23.9W
Metric Base Head Δ
settle_mean_s 50.4 78.8 +56%
settle_p95_s 72.6 158.4 +118%
unsettled_events 2 3 +50%
overshoot_mean_w 81.4 82.3 +1%
overshoot_max_w 190.8 203.1 +6%
band_crossings_per_h 116.0 104.0 -10%
grid_p2p_w 105.7 101.6 -4%
steady_rms_w 19.4 23.9 +23%
mean_abs_grid_w 51.2 55.0 +7%
avoidable_import_wh 45.4 49.0 +8%
avoidable_export_wh 30.9 32.8 +6%
battery_travel_w_per_h 38445.0 36768.0 -4%
mixed_venus_b2500/eff — settle 130.6→104.7s, overshoot 262.0→258.0W, RMS 22.4→18.0W
Metric Base Head Δ
settle_mean_s 130.6 104.7 -20%
settle_p95_s 314.5 316.1 +1%
unsettled_events 5 4 -20%
overshoot_mean_w 98.5 98.6 +0%
overshoot_max_w 262.0 258.0 -2%
band_crossings_per_h 491.33 246.0 -50%
grid_p2p_w 83.0 69.9 -16%
steady_rms_w 22.4 18.0 -20%
mean_abs_grid_w 40.5 37.6 -7%
avoidable_import_wh 38.8 37.1 -4%
avoidable_export_wh 21.5 18.6 -13%
battery_travel_w_per_h 66647.0 39871.0 -40%
mixed_venus_b2500/fair — settle 110.8→76.2s, overshoot 253.4→244.9W, RMS 23.2→19.0W
Metric Base Head Δ
settle_mean_s 110.8 76.2 -31%
settle_p95_s 211.3 180.5 -15%
unsettled_events 2 0 -100%
overshoot_mean_w 158.6 153.5 -3%
overshoot_max_w 253.4 244.9 -3%
band_crossings_per_h 348.0 93.33 -73%
grid_p2p_w 101.1 45.3 -55%
steady_rms_w 23.2 19.0 -18%
mean_abs_grid_w 40.8 37.4 -8%
avoidable_import_wh 44.4 40.3 -9%
avoidable_export_wh 17.0 15.2 -11%
battery_travel_w_per_h 64590.0 32280.0 -50%
single_venus_solar — settle 12.0→11.8s, overshoot 45.6→28.5W, RMS 15.2→15.9W
Metric Base Head Δ
settle_mean_s 12.0 11.8 -2%
settle_p95_s 15.3 13.6 -11%
unsettled_events 0 0 =
overshoot_mean_w 27.4 17.1 -38%
overshoot_max_w 45.6 28.5 -38%
band_crossings_per_h 4.0 5.33 +33%
grid_p2p_w 31.7 35.7 +13%
steady_rms_w 15.2 15.9 +5%
mean_abs_grid_w 15.1 15.9 +5%
avoidable_import_wh 13.6 14.0 +3%
avoidable_export_wh 9.0 9.9 +10%
battery_travel_w_per_h 8467.0 6670.0 -21%
single_venus_steps — settle 12.2→12.5s, overshoot 19.3→18.6W, RMS 14.4→14.5W
Metric Base Head Δ
settle_mean_s 12.2 12.5 +2%
settle_p95_s 15.8 16.2 +3%
unsettled_events 0 0 =
overshoot_mean_w 18.0 15.7 -13%
overshoot_max_w 19.3 18.6 -4%
band_crossings_per_h 9.0 9.0 =
grid_p2p_w 27.8 22.7 -18%
steady_rms_w 14.4 14.5 +1%
mean_abs_grid_w 41.6 41.4 -0%
avoidable_import_wh 24.9 26.0 +4%
avoidable_export_wh 16.7 15.4 -8%
battery_travel_w_per_h 30231.0 19302.0 -36%
single_venus_washer — settle 0.0→0.0s, overshoot 0.0→0.0W, RMS 92.3→66.2W
Metric Base Head Δ
settle_mean_s 0.0 0.0 =
settle_p95_s 0.0 0.0 =
unsettled_events 0 0 =
overshoot_mean_w 0.0 0.0 =
overshoot_max_w 0.0 0.0 =
band_crossings_per_h 828.0 500.0 -40%
grid_p2p_w 340.5 241.0 -29%
steady_rms_w 92.3 66.2 -28%
mean_abs_grid_w 67.6 42.9 -37%
avoidable_import_wh 22.0 11.8 -46%
avoidable_export_wh 11.8 9.7 -18%
battery_travel_w_per_h 121485.0 61928.0 -49%
two_venus/eff — settle 24.8→15.4s, overshoot 187.9→187.1W, RMS 15.8→14.5W
Metric Base Head Δ
settle_mean_s 24.8 15.4 -38%
settle_p95_s 61.3 21.8 -64%
unsettled_events 0 0 =
overshoot_mean_w 111.3 93.6 -16%
overshoot_max_w 187.9 187.1 -0%
band_crossings_per_h 152.0 41.0 -73%
grid_p2p_w 44.2 25.7 -42%
steady_rms_w 15.8 14.5 -8%
mean_abs_grid_w 37.5 36.3 -3%
avoidable_import_wh 23.0 23.4 +2%
avoidable_export_wh 13.7 12.1 -12%
battery_travel_w_per_h 37672.0 24408.0 -35%
two_venus/fair — settle 15.3→15.1s, overshoot 182.3→182.3W, RMS 15.0→15.6W
Metric Base Head Δ
settle_mean_s 15.3 15.1 -1%
settle_p95_s 17.5 20.3 +16%
unsettled_events 0 0 =
overshoot_mean_w 158.4 154.8 -2%
overshoot_max_w 182.3 182.3 =
band_crossings_per_h 39.0 32.0 -18%
grid_p2p_w 20.1 20.3 +1%
steady_rms_w 15.0 15.6 +4%
mean_abs_grid_w 35.3 35.9 +2%
avoidable_import_wh 22.8 23.6 +4%
avoidable_export_wh 11.7 11.5 -2%
battery_travel_w_per_h 22219.0 20901.0 -6%
two_venus_solar/eff — settle 28.1→18.4s, overshoot 202.9→205.6W, RMS 19.0→20.1W
Metric Base Head Δ
settle_mean_s 28.1 18.4 -35%
settle_p95_s 90.5 44.2 -51%
unsettled_events 1 0 -100%
overshoot_mean_w 112.7 114.1 +1%
overshoot_max_w 202.9 205.6 +1%
band_crossings_per_h 54.67 57.33 +5%
grid_p2p_w 51.7 54.1 +5%
steady_rms_w 19.0 20.1 +6%
mean_abs_grid_w 41.9 41.9 =
avoidable_import_wh 32.6 32.4 -1%
avoidable_export_wh 28.8 29.0 +1%
battery_travel_w_per_h 27990.0 27051.0 -3%
two_venus_solar/fair — settle 46.9→31.1s, overshoot 189.3→185.8W, RMS 20.3→22.0W
Metric Base Head Δ
settle_mean_s 46.9 31.1 -34%
settle_p95_s 159.3 102.9 -35%
unsettled_events 4 2 -50%
overshoot_mean_w 158.6 148.6 -6%
overshoot_max_w 189.3 185.8 -2%
band_crossings_per_h 41.33 38.67 -6%
grid_p2p_w 63.9 63.7 -0%
steady_rms_w 20.3 22.0 +8%
mean_abs_grid_w 42.4 43.6 +3%
avoidable_import_wh 35.3 35.5 +1%
avoidable_export_wh 27.0 28.5 +6%
battery_travel_w_per_h 27118.0 26031.0 -4%

📊 Open the interactive reportsteering-eval-report.html, a single self-contained file (opens in-browser; download it if your browser blocks inline scripts).

@tomquist tomquist force-pushed the claude/washer-oscillation-fix branch from a3ab29b to 93ada34 Compare June 13, 2026 15:58
@tomquist tomquist changed the base branch from develop to claude/smooth-solar-curve June 13, 2026 15:58
Base automatically changed from claude/smooth-solar-curve to develop June 13, 2026 16:00
Under a laggy meter the gain-1 grid-following residual limit-cycles: the
controller keeps reacting to a stale reading that doesn't yet reflect its
last command, so the grid hunts continuously instead of settling. Add
oscillation-gated damping to the per-battery residual: an accumulating score
tracks how often the residual reverses sign (the signature of hunting, not of
a real load change) and bleeds the loop gain by up to osc_damp_max as it
rises. Two guards keep genuine reactions at full speed: a magnitude threshold
(a residual past osc_damp_threshold is a real demand step, never damped) and
the accumulation itself (a one-off zero-crossing, e.g. a solar ramp, barely
moves the score).

Tuned on the steering-eval suite (max=0.8, alpha=0.15, decay=0.1,
threshold=450). 3-seed means vs the undamped baseline: the washing-machine
oscillation scenario's grid swing drops ~30% and its band-crossing rate ~40%,
and several multi-battery scenarios also settle faster with less hunting and
overshoot; the one notable trade is a small overshoot rise on the
mixed-cadence fair-share scenario (slow + fast poller sharing one phase).
Tune or disable with OSC_DAMP_MAX / OSC_DAMP_ALPHA / OSC_DAMP_DECAY /
OSC_DAMP_THRESHOLD (osc_damp_* on ESPHome).

Both balancer implementations (Python and the ESPHome C++ mirror) changed in
lockstep; covered by new unit tests and the existing differential parity
suite.
@tomquist tomquist force-pushed the claude/washer-oscillation-fix branch from 93ada34 to 0653acd Compare June 13, 2026 16:00
@tomquist tomquist marked this pull request as ready for review June 13, 2026 16:06

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
tests/test_balancer.py (1)

656-669: 💤 Low value

Test helper uses osc_damp_alpha=0.25 instead of production default 0.15.

The _lb helper sets osc_damp_alpha to 0.25, but the production default in BalancerConfig and the PR objectives both specify 0.15. This appears intentional (the comment at line 705 explicitly calculates with alpha=0.25), likely to make damping effects more pronounced in tests. Consider adding a brief comment here explaining why the test uses a non-default alpha value to avoid confusion.

📝 Suggested clarification
     def _lb(self, **cfg):
+        # Use alpha=0.25 (higher than production 0.15) to make damping effects
+        # more visible in test assertions.
         cfg.setdefault("osc_damp_max", 0.8)
         cfg.setdefault("osc_damp_alpha", 0.25)
         cfg.setdefault("osc_damp_decay", 0.1)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_balancer.py` around lines 656 - 669, The test helper _lb sets
osc_damp_alpha to 0.25 which differs from the production/default 0.15; add a
brief inline comment in the _lb function next to the
cfg.setdefault("osc_damp_alpha", 0.25) line explaining that the higher alpha is
intentional for tests (to amplify damping behavior and match the calculations in
the nearby test that assume alpha=0.25), so future readers understand this
deviation from BalancerConfig's default.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Line 5: The new standalone bullet about "oscillation-damped" per-battery
corrections should not be added as a separate entry under the "## Next" section;
instead, edit the existing single summary bullet under the "## Next" heading in
CHANGELOG.md to incorporate this change (mentioning OSC_DAMP_MAX /
OSC_DAMP_ALPHA / OSC_DAMP_DECAY and the user-visible flags `osc_damp_*`) so
there remains only one summary entry. Locate the added bullet text (the
paragraph explaining continuous grid oscillation and the new options) and merge
its key outcome and knobs into the preexisting "## Next" summary sentence,
removing the extra bullet.

In `@src/astrameter/ct002/balancer.py`:
- Around line 344-348: The reset_consumer() implementations must clear the new
oscillation state: set osc_score = 0.0 and osc_last_sign = 0 (in addition to
existing pacing/saturation resets) so stale damping memory is not reused; update
both the Python reset_consumer() implementation (where osc_score and
osc_last_sign are defined/used) and the ESPHome reset_consumer() equivalent to
explicitly zero these two fields.

In `@src/astrameter/ct002/ct002.py`:
- Around line 182-185: The constructor now accepts osc_damp_threshold but the
instantiation in src/astrameter/main.py still only passes osc_damp_max,
osc_damp_alpha, and osc_damp_decay, so any configured threshold is ignored;
update the construction call in main.py to pass osc_damp_threshold (e.g.,
osc_damp_threshold=config.osc_damp_threshold or the local variable used for
other osc_* settings) as a keyword argument to match the CT002/constructor
signature, and keep a fallback to 450 if the config value is missing
(osc_damp_threshold=cfg.get('osc_damp_threshold', 450)).

In `@src/astrameter/main.py`:
- Around line 185-187: The CT002 config never reads or exposes
OSC_DAMP_THRESHOLD, so update the config reading in main.py alongside
osc_damp_max/osc_damp_alpha/osc_damp_decay by adding: read osc_damp_threshold
via cfg.getfloat(...) (fallback 450) and pass it into the CT002(...) constructor
as osc_damp_threshold=osc_damp_threshold (this ties into the BalancerConfig
behavior that bypasses damping when abs(residual) > osc_damp_threshold); also
update web_config.py's CT002 schema to include OSC_DAMP_THRESHOLD (matching the
pattern used for osc_damp_max/alpha/decay) so the value is editable from the web
UI.

In `@src/astrameter/web_config.py`:
- Around line 297-299: Add the missing OSC_DAMP_THRESHOLD entry to the CT002 web
config metadata block alongside the existing OSC_DAMP_MAX / OSC_DAMP_ALPHA /
OSC_DAMP_DECAY keys so the web editor will render and validate that knob; define
it as a float with a non-negative minimum (e.g. {"type":"float","min":0}) to
allow the runtime's 450 W default to be overridden via the web UI. Target the
same metadata dict where OSC_DAMP_MAX/OSC_DAMP_ALPHA/OSC_DAMP_DECAY are defined
and insert the OSC_DAMP_THRESHOLD key there.

---

Nitpick comments:
In `@tests/test_balancer.py`:
- Around line 656-669: The test helper _lb sets osc_damp_alpha to 0.25 which
differs from the production/default 0.15; add a brief inline comment in the _lb
function next to the cfg.setdefault("osc_damp_alpha", 0.25) line explaining that
the higher alpha is intentional for tests (to amplify damping behavior and match
the calculations in the nearby test that assume alpha=0.25), so future readers
understand this deviation from BalancerConfig's default.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 936f8072-5129-4d26-b600-d07d89bbd3e2

📥 Commits

Reviewing files that changed from the base of the PR and between d63dc62 and 0653acd.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • esphome/components/ct002/__init__.py
  • esphome/components/ct002/balancer.cpp
  • esphome/components/ct002/balancer.h
  • esphome/components/ct002/test_hooks.cpp
  • src/astrameter/ct002/balancer.py
  • src/astrameter/ct002/ct002.py
  • src/astrameter/main.py
  • src/astrameter/web_config.py
  • tests/test_balancer.py

Comment thread CHANGELOG.md Outdated
Comment thread src/astrameter/ct002/balancer.py
Comment thread src/astrameter/ct002/ct002.py
Comment thread src/astrameter/main.py
Comment thread src/astrameter/web_config.py
claude added 2 commits June 13, 2026 16:27
- reset_consumer now zeroes osc_score / osc_last_sign (Python + ESPHome C++)
  so stale hunting memory isn't reused after a consumer returns to auto /
  resumes from inactive, matching the pacing/saturation resets beside it.
- Wire OSC_DAMP_THRESHOLD through the config.ini path: main.py reads it
  (fallback 450) and passes it to CT002, and web_config.py exposes it next to
  OSC_DAMP_MAX/ALPHA/DECAY so it's editable from the web UI (it was already a
  CT002/BalancerConfig parameter but unreachable from config).
- CHANGELOG: fold the oscillation-damping entry into the existing multi-battery
  "hunting" bullet (one summary bullet) and list the OSC_DAMP_* knobs.
- test: note why the damping test helper uses alpha=0.25 vs the 0.15 default.
Add the oscillation-damping knobs (OSC_DAMP_MAX/ALPHA/DECAY/THRESHOLD, ESPHome
osc_damp_*) to the CT_BALANCER group in web/ts/schema.ts so the config
generator renders them alongside the other active-control tuning fields and
emits them for both config.ini and the ESPHome balancer block. cd web && npm
run check passes.
@github-actions

github-actions Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-06-13 16:53 UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants