Skip to content

Feature/canister empty detection - #238

Merged
lostcontrol merged 7 commits into
masterfrom
feature/canister-empty-detection
Oct 25, 2026
Merged

lostcontrol merged 7 commits into
masterfrom
feature/canister-empty-detection

Conversation

@lostcontrol

Copy link
Copy Markdown
Owner

Summary

Adds automatic detection of empty chemical canisters (10–15L liquid chlorine and acid) based on dosing pump duty-cycle trends observed across discontinuous filtration cycles.

Key Changes

  • Canister Monitor (CanisterMonitor):
    • Computes peak and mean pump duty cycle for each filtration run.
    • Automatically slices long continuous runs into cycles and ignores short aborted runs (< 30 min).
    • Detects persistent upward trends or prolonged saturation.
    • Defaults to a 3-cycle window (~12–18 hours) with adaptive delta scaling (min_delta_p = 0.07), detecting depletions rapidly while avoiding false positives.
    • Automatically clears (0) on canister refill as soon as duty cycle recovers.
  • Persistence & MQTT:
    • Publishes retained status to /status/disinfection/{ph,cl}/canister_empty.
    • Restores active empty states upon Poupool restart via retained MQTT topics without duplicate alerts.
  • Home Assistant:
    • Adds binary sensors (device_class: problem) in home-assistant/mqtt.yaml for both canisters (ready for HA automations).
  • Configuration:
    • Adds configurable canister_cycles = 3 in config.ini under [disinfection].

Verification

  • Unit Tests: 110 passed (uv run pytest).
  • Linters & Types: uv run ruff check . and uv run ty check both pass with 0 errors.
  • 1-Year Dataset Simulation (history.csv, 559 cycles): Accurately detects empty canister events (including October 19–24 runout within ~13 hours) matching the pool owner's ~6 annual canister changes.

@github-actions

Copy link
Copy Markdown

Coverage

Coverage Report
FileStmtsMissCoverMissing
controller
   actor.py681282%44–45, 67–69, 86, 92–96, 116
   arduino.py46460%18–83
   config.py12192%33
   device.py29116543%44, 47, 50, 53, 56–57, 64–65, 68–69, 72, 78, 81, 91, 95, 117–122, 125, 128, 131–134, 145–147, 177, 182, 189–192, 195–196, 201–221, 226–229, 233–245, 256–266, 269–271, 276–277, 280–298, 303–309, 312–314, 318–319, 322, 325, 328, 333–334, 339, 342–371, 374–397, 402–405, 408, 412
   disinfection.py2783886%65, 67, 88–89, 182–184, 217, 250, 310–311, 314–315, 318–319, 322–323, 327–328, 331–332, 335–337, 345, 358, 363–365, 368–372, 385, 398–400
   dispatcher.py38380%18–343
   filtration.py61542531%75, 83–85, 95, 98, 101, 107, 119–121, 130, 133, 141–148, 151–156, 159–164, 167–171, 174–177, 180, 183–192, 224–312, 315–318, 321–326, 329–331, 334–335, 338–339, 342–344, 347, 350, 353–355, 358–359, 362–367, 370–375, 378–383, 386–390, 393–394, 397–398, 401–405, 408–409, 412–414, 417–419, 422–424, 427–428, 431, 434, 437–443, 446, 449, 452–454, 457–464, 467–480, 483–484, 488–496, 499–510, 513–517, 521–529, 532–540, 543–547, 550–555, 558–567, 571–575, 578–590, 594–602, 605–610, 613, 617–621, 624–627, 630–633, 636–637, 642, 645, 648, 652–656, 659–669, 672–673, 676–682, 686–696, 699–701, 704–710, 713, 717–723, 726–730, 733–737, 740–741, 744–746, 749–754, 758–771, 774–775, 778–779, 782–784, 787–795, 798–801, 804–807, 810–815, 819–821, 824–829, 832–835, 838–840
   heating.py1951950%18–282
   lcd.py641281%51–59, 66, 102–103
   light.py24240%18–52
   mqtt.py48480%18–81
   sensor.py1034259%38, 47, 56, 60, 63–66, 74–75, 78, 81, 84–85, 92–94, 97–103, 115, 118, 132–133, 140–142, 145–157
   swim.py822570%63–64, 77–78, 87–89, 92–97, 110–112, 115–120, 123–126
   tank.py1121120%18–165
TOTAL2061118343% 
Tests Skipped Failures Errors Time
110 0 💤 0 ❌ 0 🔥 18.694s ⏱️

@lostcontrol
lostcontrol merged commit c504ec2 into master Oct 25, 2026
1 check passed
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.

1 participant