Changelog
The integration and the controller are released as a matching pair. This page is the project's two changelog files from release v2.19.2, shown as written.
Integration
All notable changes to the Advanced Automated Crop Steering System will be documented in this file.
Two views per release. Each version leads with ๐ฑ In plain English โ what changed and why it matters, written so anyone can follow it without knowing the internals โ followed by ๐ง Technical notes, the entity- and code-level detail for developers and AI agents working on the repo.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[2.19.2] - 2026-09-21
Pair: controller 0.16.2. Class C3. Everything here comes from two reviews by use: the first real install on a one-zone tent, set up from a phone, and an independent review of 2.19.1 upstream. Nine small changes, each its own pull request with its own tests (#15 to #23). This candidate carries more than one behaviour change (two C3, four C2), which docs/RELEASING.md says a candidate should not; they were bundled by decision of the person running the only staging room, and a failed soak would have to be bisected across them. The defects were seen on real hardware; the fixes have not run on hardware before release. Update with the engine off, read the controller log, then watch the first shot.
๐ฑ In plain English#
- Set things up in either order. If the controller app was started before the integration was set up (the order the app store invites), a one-zone tent was shown Zones 2 and 3 that do not exist, each complaining "no hardware mapped", and a minute after setup Settings > Repairs told you to create a kill-switch helper by hand. Both are gone. The controller now waits for a room, invents nothing, and picks the room up by itself within a minute, with no restart. If you saw that Repairs card: do not create the helper. A room made by the wizard has its own kill switch, Engine Enabled; the helper would be a second one that does nothing.
- A zone is called what you called it. Name a zone "GT1" and its device in Home Assistant was still "Zone 1" or "Crop Steering Zone 1", depending on which part of the integration got there first. It is now the name you typed, and renaming the zone in Configure renames the device. A device you renamed yourself in Home Assistant keeps your name. No entity id changes.
- Switching a room on no longer creates an irrigation event. New switch-on timestamps are marked explicitly and displayed as unknown until an irrigation is recorded. Older unmarked timestamps are preserved: zero daily counters or missing history cannot establish whether an old timestamp represented irrigation or switch-on. Electrical operation does not prove water delivery.
- The menu scrolls on a phone. On a small screen the lower half of the side menu (including Help & tools and the version numbers) could not be reached.
- Configure > Edit parameters no longer locks out a room that steers dry. The number entities accept a P1 target and a P2 threshold as low as 5 %, the form insisted on 30 % and 25 %, and since 2.18.1 it opens on your current values. A room at P1 20 % could not submit the form even unchanged. The form now has exactly the limits of the entities it edits.
- Assistants using the MCP tools can change how a room is plumbed. A room that had declared its plumbing could never gain or lose its pump through them: the tools did not know the question existed. The plumbing and the pump or main-line mapping now travel together in one reviewed proposal, and a proposal that contradicts the declared plumbing is refused at preview.
- Two holes in the release checks are closed. A release pull request could also change a default, a dependency or an add-on permission inside the three files that hold the version numbers; and a controller-only release was accepted that could then never be promoted. Every release now raises the integration's number, a controller-only fix included.
๐ง Technical notes#
- Zones are never invented (#17, C3). The shipped add-on options carry
num_zones: 3, documented as "only used if Home Assistant isn't reachable at startup" but also used when Home Assistant answered and the integration simply was not set up yet. New_default_zone_ids(options, descriptor) -> (ids, provisional): the descriptor's authoritativeactive_zone_ids/num_zonesfirst, then fused sensors (provisional without a descriptor), else a hand-mappedhardwareoption keepsnum_zones, else no zones when Home Assistant answers, else the documented fallback. While provisional the controller rediscovers every loop and re-resolves zones, enable flag and feed sensors. A state file that already holds phantom zones still loads. It also stops the controller's pre-setupzone_N_statusstates pushing the integration's own status sensor ontosensor.crop_steering_zone_1_status_2on a first install. - Last irrigation is an event (#18, C3). New per-zone state field
last_shot_is_anchor(additive:_fresh_zonedefaultFalse; an old file without it retains its existing timestamp, because daily counters reset and history expires)._room_switched_onstill stampslast_shotso the blind-probe schedule counts from switch-on;zone_N_last_irrigation_apppublishesunknownwhile the flag is set, including for a room that is off. No option or entity change. Final review added regression coverage for partial sensor startup, legacy numeric strings and malformed excluded-volume values, and genuine irrigation timestamps after daily rollover. - The kill-switch repair judges the right switch (#19, C2).
health._kill_switch()trusted the heartbeat'senable_flagover the room's own descriptor. While the heartbeat reports an oldersetup_revisionthan the descriptor (both plain ints), the engine holds every zone anyway and adoption moves it to the descriptor's flag, so that flag is the one judged. An up-to-date engine is believed as before (custom add-onenable_flag), a deleted legacy helper is still reported, and a controller too old to report a revision is believed as before. - Zone device name (#16, C2).
room.zone_device_name(entry, zone_num), used by the button, number and select platforms, which used to register the same device under two different names. The room device is left alone. - Edit parameters limits (#20, C2). The four fields read
native_min_value/native_max_valuefromNUMBER_DESCRIPTIONS. Only the lower limits ofp1_target_vwc(30 -> 5) andp2_vwc_threshold(25 -> 5) actually move. - MCP plumbing (#21, C2,
mcp-server/only).plumbingin the strict input schema; the room carriesplumbing("" = never declared) andplumbing_inferred;config()includes the layout only when declared, which puts it in the payload, the comparison snapshot and the readback digest, and never declares one on the operator's behalf. Newtests_ha/test_mcp_setup_contract.pysends that payload to the realsetup_save. - Sidebar scroll (#15, C1).
.desktop-sidebar/.mobile-sidebargetoverflow-y: autoandoverscroll-behavior: contain, their childrenflex-shrink: 0; checked inverify-live.mjsat 390x640 and 1280x480. The committed bundle is rebuilt from that source. - Release guards (#22, #23, C0).
without_version()compares each version file with only its version field blanked, andcheck_pull_requesttakes the files' text as a required argument; a release that does not change the integration version is refused, and a release branch is named for the integration version. Both take effect once promoted, because GitHub reads the workflow frommain. - Upgrade in place. No add-on option and no entity id changes. One additive state-file field
(
last_shot_is_anchor). A room that never declared its plumbing still publishes byte-for-byte the descriptor it did, and the controller's saved setup fingerprint still matches, so an update resumes without a disarm cycle. Every change above is tested both on a fresh install and from the seeded snapshots of old installs intests_ha/fixtures/. - Documented, not changed. A probe whose reading has not changed for 20 minutes is treated as dead and the zone goes onto the blind schedule. That is deliberate (a probe pulled out of its cube leaves a plant that will need saving), and it also fires on a bench test with no plant in the cube. docs/troubleshooting.md now says so, along with the three first-install symptoms fixed above.
[2.19.1] - 2026-09-21
Pair: controller 0.16.1. Class C3 by the table, because the controller is touched, though what changes there is one new attribute on a sensor it already publishes: no change to irrigation behaviour, options, the state file or any entity id. Not run on hardware before release: update with the engine off, then check the sidebar reads 2.19.1 and 0.16.1.
๐ฑ In plain English#
- Crop Steering has its icon. Adding the integration, the integrations page and HACS all showed a grey "icon not available" box. The integration now carries its own icon and logo, with versions that stay readable on a dark theme. Needs Home Assistant 2026.3 or newer; older versions carry on showing the placeholder, and nothing else changes for them.
- You can see what you are running without leaving the dashboard. The sidebar, under Help & tools, now shows the integration version and the controller version. Both come from the parts that are actually running, not from the page, so it cannot show a version you have not got, and a half that is too old to say (or a controller that is not running) reads "not reported". After an update it is the quickest check that both halves really moved.
๐ง Technical notes#
- New
custom_components/crop_steering/brand/(icon,logo,@2x, anddark_variants), which Home Assistant 2026.3+ serves for a custom integration with no manifest change. Built fromimg/crop-steering-logo.pngbyscripts/make_brand_images.py: the icon is the emblem alone (it is shown at about 40 px), the logo keeps the wordmark, dark variants sit on a white rounded tile, all reduced to 256 colours with alpha (10-53 KB each).tests/test_brand_images.pypins names and sizes from the PNG headers. Class C1: nothing the controller reads. - Versions in the sidebar. The integration publishes
integration_versionin each room'sengine_configdescriptor (fromSOFTWARE_VERSION, already tied tomanifest.jsonby the version tests). The controller publishescontroller_versionin each room'sai_heartbeatand logs it on start; it reads the number from theconfig.yamlit was built from, which the Dockerfile now copies into the image as/app/addon.yaml. No number is duplicated anywhere, so a release has nothing extra to bump; the tests fail if the report andconfig.yamldisagree or the Dockerfile stops shipping the file. The descriptor attribute is proven not to enter the controller's setup fingerprint, so an integration update still resumes without a disarm cycle. Class C3 by the table (the controller and its Dockerfile are touched), though the change is one new attribute on a sensor the controller already publishes: no state-file, option or entity-id change.
[2.19.0] - 2026-09-21
Pair: controller 0.16.0. It also carries 2.18.1 / controller 0.15.2, which was never published by itself. Class C3. Released without a staging soak by decision of the two people who run it; see the record. Not run on hardware before release: treat the first update of each box as the first run. Engine off, update, check the log, watch the first shot.
๐ฑ In plain English#
- Setup now asks how your room is plumbed, and holds you to the answer. 2.18.0 worked it out from what you left empty: no pump chosen meant "this room has no pump". That is right for a tent on one smart plug. It is silently wrong for a room that does have a pump and whose pump was never chosen, or was cleared by mistake: the controller opened the valve, ran no pump, and counted the water as delivered while the plants got none. The wizard and Rooms & setup now ask the question outright (zone valves only; a pump, then zone valves; a main-line valve, then zone valves; or all three), and the switches you choose have to match your answer.
- If they ever stop matching, the room is held, with a reason. A room that says it has a pump and has none mapped is not watered, nothing is counted as delivered, and you get a notification saying what to fix. It used to look healthy until the plants wilted.
- Nothing changes for a room that is already set up. It carries on exactly as it does today, and an update does not need the kill switch cycled. Open Rooms & setup (or Configure) when it suits you: it shows what your current switches imply and asks you to confirm. From then on the protection above applies to that room too.
๐ง Technical notes#
- New optional
plumbingin a room's setup and, only when declared, in the engine descriptor:valves_only,pump_valves,mainline_valves,pump_mainline_valves(custom_components/crop_steering/plumbing.py; the controller carries the same table and a test reads both).prepare_setuprefuses a save whose mapped switches contradict a declared layout, both ways, for an active room. Once declared it can be changed, not withdrawn; a client that does not send it keeps the stored value. - Controller:
plumbing_hold()gates_blockedand, as a last line of defence,_execute_shot. A contradiction or a layout the controller does not know holds the room, alerts (f2_plumbing_<room>, at most every 30 minutes), opens nothing and counts nothing. A mapped switch the declaration disowns is still safed on exit and still has to read OFF for adoption. - Upgrade in place: an undeclared room publishes byte-for-byte the descriptor it did (key set
pinned in
tests/test_plumbing.py), andplumbingjoins the setup fingerprint only when present, so the fingerprint saved by controller 0.15.x still matches and the room resumes without a disarm cycle. Proven intests_ha/from seeded snapshots: a one-switch tent recorded by running upstream 2.18.0's own wizard (fixtures/entry_2_18_one_switch_tent.json, fingerprint computed by controller 0.15.1's code) and the 2.17 pumped room. Declaring later is an ordinary setup change (new revision, adopted with the kill switch and hardware OFF). - Wizard and Configure: a required list question at the top of the hardware step; no prefill for a
new room; Configure prefills the declared layout or what the saved switches imply. Clearing the
pump of a room declared with one is refused in the form. Rooms & setup asks only when
setup_readreports theplumbingcapability, so a newer dashboard served by the add-on beside an older integration neither asks nor sends it. - Not changed: the env-file path (rooms configured that way stay undeclared until saved through a form), add-on options, the state file format, every entity id.
- Not run on hardware. Everything above is proven against a real Home Assistant and the real controller code with a fake switch layer. No physical pump or valve has been driven by this build.
[2.18.1] - 2026-09-21
Pair with controller 0.15.2 (0.
Pair with controller 0.15.2 (0.15.1 plus a test-only seam; irrigation behaviour is unchanged). Bug fixes only. Nothing an operator has set moves: every fix below was checked against seeded snapshots of older installs. Each was reproduced on 2.18.0 inside a real Home Assistant before it was fixed; the write-up is docs/audits/2026-09-21-first-run-review.md.
๐ฑ In plain English
- The integration starts on older Home Assistant. On anything before Home Assistant 2026.5 the setup wizard finished and the integration then showed "Failed to set up": no entities, no dashboard. It lists 2024.3 as supported, and now it is.
- Your lights times are used. The wizard asks when your lights turn on and off, stored the answer, and then always ran on 12 and 0. The grow-day, the morning dry-back and the overnight phase now follow the hours you typed. If you already set them on a dashboard, those are kept.
- "Edit parameters" does something. Changing a value under Configure said "saved" and quietly put the old value back. It now changes what the controller reads, and the form opens on the current value rather than the one from the day you installed.
- A sensor or pump can be removed, not just swapped. Under Configure you could replace a mapping but never clear it: it came straight back. That matters more now that a pump is optional.
- Boxes that did nothing say so. The waste-valve box promised the valve is "forced closed during a shot". The controller has never operated it, so if your plumbing relies on that, arrange it yourself. The grow-light, notification, humidity and VPD boxes are likewise marked as recorded only.
- The pump box says what leaving it empty means. Since 2.18.0 an empty pump is accepted and the controller then never runs one: it opens the zone switch and counts the shot as delivered. That is right for a one-switch tent and wrong for a room that has a pump, so the box now says so where you choose it.
- Every box is explained. The feed EC and feed pH pickers showed their raw names (
feed_ec_sensor) with no label at all, and each zone's name and "in use" box had no help text. Two Configure messages showed as raw keys. - Small pots can be adjusted. A 0.65 L rockwool cube was accepted by setup and then could not be edited, because the setting's minimum was 1 L.
๐ง Technical notes
setup_panel:frontend.async_panel_existswas added in Home Assistant 2026.5.0 (absent from core tags 2024.3.0, 2025.1.0, 2026.2.3, 2026.3.0, 2026.4.0) and was called unconditionally insideasync_setup_entry:AttributeError, entry stateSETUP_ERROR._panel_exists()uses the helper when it exists and otherwisePANEL in hass.data[frontend.DATA_PANELS], which is what the helper does. Neither test tier could see it:tests/test_setup_panel.pyassigns the function onto its own stub, andtests_ha/conftest.pyreplaced the whole panel registration with a no-op.tests_hanow stands in for the web server only and runs the registration against the real frontend module; with the fix reverted, that tier fails.number.PARAM_TO_ENTITY_KEYgainslights_on_hour/lights_off_hour. A seed only applies to an entity being created for the first time (RestoreEntitywins afterwards); pinned by a seeded upgrade where setup recorded 10-22, the operator set 8-20, and 8-20 survives.OptionsFlowHandler.async_step_edit_parameterscallsnumber.set_valueon the live entities before_update, so the reload restores the value just written; defaults come from the live entities. The OFF check and its abort are unchanged, and nothing is written when it refuses._hardware_schema._entprefills withdescription={"suggested_value": ...}instead ofdefault=. The frontend omits an emptied field and voluptuous re-applied the default.test_native_hardware_schema_retains_explicit_tank_telemetrynow asserts the same intent (the form opens showing the mapping) and additionally that the field can be cleared.- Translations:
options.abort.not_env_config/reload_failed; labels and tooltips forfeed_ec_sensor/feed_ph_sensoron both mapping forms; tooltips for all 24zone_N_name/zone_N_active; truthful text forwaste_switch,light_entity,notification_service,humidity_sensor,vpd_sensor(no runtime consumer in the integration, the add-on or the engine) and forpump_switch. - Global
substrate_volumeminimum 1.0 -> 0.1 anddrippers_per_plantmaximum 6 -> 20, matchingsetup_api.SIZINGand the per-zone entities. - Tests, lean:
tests/test_translations.py(every abort reason, error key and menu entry has a message in the flow that raises it; every mapping-form field has a label and a tooltip; hassfest's own key, quoted-placeholder and orphan-tooltip patterns; a field with no runtime consumer may not promise behaviour, and fails the day one gains a consumer). An honest pre-2026.5 case intests/test_setup_panel.py. - Tests, real Home Assistant (
tests_ha/, 4 -> 30):test_setup_entry.py,test_configure.py,test_upgrade_in_place.pydriven by seeded snapshots intests_ha/fixtures/(a 2.17 wizard room and an env-file era room: tuned values, an operator-renamed entity, a unit-less probe, and controller 0.14's saved setup fingerprint resuming with the kill switch ON), andtest_install_to_controller.py, which hands a freshly installed room to the real add-on controller and requires it to find, adopt and water it. - Controller test seam:
F2_STATE_PATH(see the add-on changelog). Docs:docs/TESTING.mddescribes the real-Home-Assistant tier, the fixtures and running hassfest without Docker. - Not changed, raised for a decision: an unmapped pump is read as "this room has no pump" (2.18.0). See the audit for a demonstration and three options.
[2.18.0] - 2026-09-21
Pair with controller 0.15.1 (0.
Pair with controller 0.15.1 (0.15.0 plus one fix: a room switched off stays off while Home Assistant restarts).
๐ฑ In plain English
- The setup wizard no longer throws your work away. If something was wrong at the end (a valve that was on, a probe in the wrong unit, a mistyped entity), the wizard closed and every zone, sensor and size you had entered was gone. It now shows the same step again with everything still filled in and says what to fix. Problems are reported on the step where you entered them, not three screens later, and the message says whether the entity is on, unreachable or does not exist instead of always "must read OFF".
- A brand-new install is found by the controller. On a fresh install Home Assistant named most of this integration's entities from their labels (
number.p1_target_vwc,sensor.engine_config) instead of thecrop_steering_ids the controller and dashboard read, so a new room was never discovered and never watered. New installs and new rooms now register under the documented ids. Existing rooms are untouched: Home Assistant keeps the ids it already holds. If you first installed on 2.17 or earlier and your room was never found, update, then remove the room and add it again. - A tent with one switch works. A room whose only hardware is one smart plug or solenoid per zone saved fine and then never watered, because the controller insisted on a separate pump and main-line valve. A zone now needs only its valve; pump and main-line are used when you have them. Every safety check still applies to whatever hardware the room has.
- Probes in other units are converted, not rejected. Pore EC in ยตS/cm and moisture reported as a 0-1 volume fraction are accepted and converted to mS/cm and percent, including mixed probes in one zone and the source-water EC probe.
ppmis still refused, with the reason: the 500 or 700 scale is not something a sensor reports. - The Cloudflare judge now looks after P2. With Auto Setpoints on and a Cloudflare token in the controller's options, the
typesafe/jevmodel is asked once an hour during P2 whether pore EC should be flushed or stacked and whether the peak still fits. It may nudge that zone's P2 shot size (1-4 % of substrate) and hold the working peak up to 2 points above or below the learned one: one small step per lever per grow-day, never while a dated plan owns the room. It cannot fire, size or delay a shot. If a guard trips (probe not believable, water not landing) or Cloudflare does not answer within 5 seconds, nothing changes. The zone's Auto Setpoints status shows what it said and what it changed today. - Less typing, fewer wrong numbers in Rooms & setup. Choose litres or US gallons and L/h or GPH (always saved as metric); pick a common block or pot with its litres shown; work out real dripper flow from a catch test; and see the zone's learned peak as a suggestion beside field capacity. Suggestions are never applied for you.
๐ง Technical notes
config_flow:_retry_formre-shows a step throughadd_suggested_values_to_schemawitherrors.base = setup_invalid; the zones step validates withprepare_setupandsafety_blockersbefore moving on; the reconfigure zone map does the same.safety_blockersmessages keep "must read OFF" and append the cause.- Controller: pump and mainline are optional in discovery, late mapping, setup adoption, the per-zone gate and
_execute_shot; lead times are skipped with the hardware they belong to, the close read-back and the hardware-fault latch cover the actuators that exist. The three-switch sequence and its timing are unchanged (tested). - Controller
_auto_tick: hourly P2 consult (auto_setpoints.jev_due/jev_verdict),p2_shot_sizejoinsmanagedwhile the judge is configured,working_peak = learned peak + peak_adjdrives the P1 target and the P2 threshold. New status attributesjev_last,jev_changed_today,working_peak_adjust. Learned state restores across restarts. - New
units.py: exact conversions only (ยตS/cm, Greek-muฮผS/cm,uS/cm-> mS/cm;mยณ/mยณ-> %). Unknown or missing units pass through unchanged so older installs keep their readings. The controller converts the source-water EC probe the same way before its 0-20 sanity range. number,select,sensorandbuttonsetself.entity_idfrom the object id they already computed. Home Assistant ignores_attr_object_id(the 2.17.1 switch fix, now everywhere): in a real Home Assistant 75 of a one-zone room's 134 entities registered under label-derived ids. Found by the new real-HA test job; a permanent test asserts every entity lands on the id its code asks for.- CI: a
real-home-assistantjob runstests_ha/in a real Home Assistant (pytest-homeassistant-custom-component, Python 3.13): the wizard end to end for a one-switch room, and the registered id of every entity. The sidebar panel is stubbed there (it needs the frontend wheel and a web server). The stub suite could not see the 2.17.0 entity-id bug; this can.
[2.17.2] - 2026-09-20
Pair with controller 0.14.0 (unchanged).
Documentation only; no code change. Pair with controller 0.14.0 (unchanged).
- Feature matrix: rows for the 2.17 features with what is tested and what was exercised live on 20 September (room off, restart-safe setup, patient read-back), what was not (the full P1 ramp through a live lights-on, Auto Setpoints writing live), and an updated live deployment row.
- Planning guide: "Read the combined graph" now describes the projected P0-P3 day, the overnight lines and the P2 threshold note, replacing text from before 2.16.1.
- Entity reference:
room_active,auto_setpointsand the per-zoneauto_setpointssensor. - User guide, sidebar guide and the controller's documentation tab no longer describe the Today / Schedule navigation as awaiting verification or use the old page names.
[2.17.1] - 2026-09-20
Pair with controller 0.14.0 (unchanged).
Pair with controller 0.14.0 (unchanged).
- Fix: the new Room Active and Auto Setpoints switches registered under ids made from their labels (
switch.crop_steering_room_active_off_empty_room_no_irrigation_no_alerts), which the controller and dashboard never look for, so the room on/off control did nothing on a fresh 2.17.0 install. Switches now suggestswitch.crop_steering_<prefix><key>when first registered. Found on the first live install, where the four entities were renamed in the entity registry. - If you installed 2.17.0: rename the two switches per room to
switch.crop_steering_<prefix>room_activeandswitch.crop_steering_<prefix>auto_setpointsin Settings > Entities (an entity already registered keeps its id), or remove them and restart on 2.17.1.
[2.17.0] - 2026-09-20
Pair with controller 0.14.0.
Pair with controller 0.14.0.
๐ฑ In plain English
- Room on/off. Each room has a Room Active switch. Turn it off when nothing is growing: no irrigation (scheduled, emergency or blind-probe fallback), no alerts, no repair issues, and the room's open notifications are dismissed. Turn it back on and the room starts a clean cycle from the overnight phase; water history is kept.
- P1 always runs in full. The ramp no longer ends on a clock. However late the first shot lands, P1 fires its shots in order until the target is recovered (after at least the new minimum shot count) or the maximum shot count is reached. Only then does P2 start.
- See the sensor where you set the target. The Today graph you drag targets on now draws that zone's recorded VWC and pore EC underneath them (this grow-day and the previous one), with now, peak and trough above it, on an axis scaled to the readings instead of 0-100 %. A deeper 24 h / 72 h / 7 d history panel sits below, and setpoint fields warn when a target sits outside what the probe reads.
- The whole day is drawn the way it runs. P0 keeps drying after lights-on, P1 climbs one step per shot (all of them), P2 fires a shot each time VWC falls to its threshold, and P3 dries down overnight to the next lights-on. Timing uses the zone's own measured dry-down rate, so it is a projection, not a schedule. Hover any riser for its time and size.
- A restart no longer strands irrigation. The controller used to forget which setup it had accepted whenever it restarted, then refuse to water until the kill switch was turned off and on again, without saying so. On 2026-09-20 a host reboot cost F2 two hours of its morning ramp that way. It now remembers the setup it accepted and carries on after a restart if nothing changed (pump, mainline and valves must still read off). A genuinely changed setup still needs the off-and-on, and now says so with a notification naming exactly what has to read off. The first start after this upgrade still needs one off-and-on, because the old build saved nothing to remember.
- A slow pump report no longer stops the room. After a shot the controller checks that pump, mainline and valve all read off. It used to look once, a second later, and a Zigbee plug that answered in 1.6 seconds latched a false hardware hold that stopped F2 for 16 hours. It now looks at 1 second as before and then keeps re-reading for up to 6, so a late report passes and a genuinely stuck valve still latches within the same minute.
- When P2 shows no sawtooth, the graph says why. The engine fires a P2 shot only once VWC has dried down to the P2 threshold. A threshold far under the P1 target spends the whole window drying, so the graph now draws the threshold across P2, says how many points and hours away it is, and names the threshold that gives shots from the start of P2.
- Auto Setpoints (off by default). The controller learns each zone's real ceiling, what a shot lifts it, and how fast it dries. When a P1 ramp stops rising for two shots it hands over to P2 and carries the achieved peak forward as the P1 target, then probes 1 point higher after 3 days. It only ever rewrites per-zone target numbers; the engine still decides every shot.
๐ง Technical notes
switch.crop_steering_<prefix>room_active(default on) andswitch.crop_steering_<prefix>auto_setpoints(default off).health.pyclears the room's repair issues while the room is off; the controller publishesapp_status: room_offand aroom_activeheartbeat attribute.- Engine core: P1 time exit removed;
ZoneParams.p1_min_shots(fromnumber.โฆp1_minimum_shots, clamped top1_max_shots). Both vendored copies stay byte-identical. - New pure modules in the controller:
auto_setpoints(learner),setpoint_supervisor(bounded, stepped, ladder-safe writes),curve_tracker(day planner),engine_twin(test twin around the realdecide()),jev_policy(optional Cloudflaretypesafe/jevjudge, consulted only on a plateau; any failure returns no verdict and never blocks irrigation). - Gain is learned only from ramp shots fired at least 2 points under the ceiling; dryback rates fold in once per grow-day as that day's mean. Near-ceiling top-ups no longer shrink the P2 band.
- Publishes
sensor.crop_steering_<prefix>zone_<N>_auto_setpoints(off / learning / tracking / frozen) with learned_peak, gain, day_rate, night_rate, p1_outcome, hold_days, frozen_reason, last_change, jev, managed. - Add-on options
cf_account_id,cf_api_token,cf_gateway_id(all optional). Auto Setpoints never writes while a dated plan owns the room. - Dashboard:
foldRecorded,smoothRecorded,dryRates,projectDayandplanningAxisinfrontend/src/lib/planning-curve.ts; the dryback target is measured from the projected peak (as the engine measures it from the recorded one) rather than from field capacity. Lines carry a scale-freedata-planning-valuessignature because the axis now follows the data. - Fix: the live history request had no
end_time, so Home Assistant returned only the first 24 h of a 72 h or 7 d window. - Controller: the adopted
setup_revisionis saved per room with a fingerprint of what was adopted (_setupin/data/state.json: pump, mainline, valves, enable flag, active zones, feed sensors). Same revision and fingerprint after a restart is resumed without the engine flag reading off; hardware must still read off. Malformed or missing records keep the full fail-safe. A pending setup raisesf2_setup_<room>(debounced, dismissed on adoption, silent for archived rooms), and the per-zone hold line carries[blocked: ...]in every phase. - Controller
_confirm_switches: first read at 1 s, then every 0.5 s to a 6 s deadline (CONFIRM_FIRST_READ_S,CONFIRM_POLL_S,CONFIRM_TIMEOUT_S). Regression tests cover a 1.6 s report (no latch) and a pump that never reports off (latches, bounded). - Dashboard:
p2Adviceexplains a missing or late P2 sawtooth; nominal dry-down is now 2 / 1 points per hour (lights on / off) until a zone has history, replacing 0.7 / 0.35 taken from one low-light week.
[2.16.1] - 2026-09-08
- Replace competing Manual setpoints and Grow plan navigation with Irrigation plan: Today and Schedule.
- Show effective active schedule targets in Today; hide misleading fallback controls while a schedule owns the room.
- Connect VWC and dashed EC planning references across lights-off and overnight to the next lights-on. Missing EC anchors remain gaps; overnight EC is an interpolation, not a prediction.
- Keep emergency-floor edits and saved-reference overlays independent. Preserve legacy routes and unsaved-draft navigation guards.
- Bundle the same dashboard in controller 0.13.3; no controller decision changes.
[2.16.0] - 2026-09-08
Pair with controller 0.13.2 for the bundled dashboard and timezone-aware irrigation event publication.
-
Add a local MCP connector for LLM-assisted configuration with reviewed, room-scoped proposals and opt-in writes.
-
Seed the isolated demo with synthetic named recipes and current/previous runs; live libraries remain unseeded.
-
Temporarily collapse the Home Assistant sidebar while embedded, with persistent desktop/mobile menu access and restore on leaving.
-
Add graphical room tank level, pump and fill status, tank EC/pH/temperature, and recorded fill completion time to Overview.
-
Show controller state, mapped valve status and last irrigation time in zone tables, mobile cards and details.
-
Add optional room-specific tank telemetry mappings, separate from feed-water safety gates. Unknown data stays unknown; level changes are never presented as fill events.
-
Pair with controller 0.13.2 for the bundled dashboard and timezone-aware irrigation event publication.
[2.15.0] - 2026-09-08
Pair with controller 0.13.1.
- Add an empty, room-scoped library for saving and reusing user-authored plans as local drafts. Browser storage is separate for live and demo; loading preserves the current zone start dates and uses existing plan validation/review.
- Support the legacy same-room
maximum_shot_durationentity alongside the canonical name in the controller and runtime calculator. Canonical entities take precedence; invalid configured values do not silently acquire another room's cap. - Pair with controller 0.13.1. No crop-guide numerical presets or publisher endorsement are included.
[2.14.0] - 2026-09-08
๐ฑ In plain English. See the whole day while editing setpoints: the draft VWC/EC curves and P3 emergency floor move immediately beside the saved reference. Compare retained readings over a day, week, month or run-to-date with another run at the same grow age. Water cards distinguish total zone delivery, average per plant and pot capacity, with a local runtime calculator.
๐ง Technical notes โ integration 2.14.0, controller 0.13.0.
- Add phase-focused manual editing, bounds-aware graph handles, saved/draft overlays and read-only active-plan previews.
- Add room-scoped, revisioned run metadata with captured target references and Recorder comparisons. Recorded history remains subject to retention; a reference captured today is not a historical target audit.
- Add explicit all-plant daily zone litres, per-plant averages, nominal phase-shot volumes and capped runtime estimates. P1 series budgets are conditional; daily adaptive shot counts are not predicted.
- Count new delivered litres from configured flow and elapsed runtime, including duration caps, fractional-second truncation, minimum runtimes and partial aborts. Freeze sizing per shot to prevent in-flight configuration edits changing its recorded volume. Existing totals are preserved.
- Keep all edits local until reviewed; comparison registration and runtime calculators do not activate irrigation.
[2.13.2] - 2026-09-08
- Show each room's configured name in the workspace instead of a generic sensor label.
- Package the corrected workspace in controller 0.12.1; irrigation logic is unchanged from 0.12.0.
- Document the verified in-place upgrade, preserved settings and consolidated branches.
[2.13.1] - 2026-09-08
๐ฑ In plain English. Fix a startup failure when multiple rooms load at once. Every room can now share the native sidebar reliably. Controller 0.12.0 remains the matching version.
๐ง Technical notes. Serialize sidebar/static-path registration across concurrent config-entry setup. Live installation exposed the duplicate-panel exception; deterministic concurrent-startup and retry tests cover the correction.
[2.13.0] - 2026-09-08
๐ฑ In plain English. One Home Assistant native workspace brings room setup, current readings and whole-grow planning together. The combined VWC/EC planning graph follows each zone's selected day, week and steering profile. Existing installations keep their room identities, setpoints and hydraulic settings.
๐ง Technical notes โ integration 2.13.0, controller 0.12.0.
- Replace the dashboard family with one React/shadcn operator workspace using inherited Home Assistant themes, bundled fonts and responsive layouts.
- Add reactive combined VWC/EC planning curves, recorded dual-axis history and per-zone day/week recipes with continuous steering between explicit endpoint profiles.
- Add durable plan storage, revisioned preview/save/arm/disarm services and atomic expiring controller snapshots applied at local lights-on boundaries.
- Add reviewed room/zone lifecycle and searchable sensor mapping with stable IDs, archived restoration, per-zone sizing and controller adoption status.
- Add local catch-test calculations, sensor diagnostics and equipment maps; remove unsupported yield/potency claims from the active UI.
- Correct duration accounting, volume-cap bypasses, shared-hardware fault recovery, relative-dryback timing conversion, low-flow sizing and stale sequential-plan decisions.
- Bundle the dashboard in the integration with automatic sidebar registration, publish app-repository metadata, consolidate install/operation instructions, and archive superseded assets with provenance.
- Resolve setup hydraulics from the current room/zone number entities so a rename or mapping edit preserves live plant counts, pot size and dripper settings.
- Archive unused facility examples, environment templates and disabled workflows; add current README screenshots and a public interactive demo link.
- Preserve base VWC shot sizes while EC is unknown, suspend EC adaptation, and expose degraded EC status (#37).
- Reject nonfinite or invalid/stale/future-dated feed readings; describe arithmetic sensor averaging and relative dryback accurately (#38, #39).
- Persist timed manual override deadlines across restart/reload and cancel obsolete callbacks on retrigger/manual changes (#40).
- Track rolling seven grow-day delivery estimates with explicit partial-history coverage; missing weekly sources remain unknown (#41).
- Audit all branch tips and retain recoverable archives; use tracked-only controller release packaging.
- See docs/FEATURE_MATRIX.md for validation evidence and live commissioning limits.
[2.12.0] - 2026-07-03
๐ฑ In plain English. This release makes the system genuinely portable to any Home Assistant โ not just the facility it was built on โ and closes the production-audit findings (#14โ#31). The biggest changes you'll feel: the engine now learns your pump and valves from the integration's setup wizard (no more hidden facility defaults), the kill switch stops water mid-shot within a couple of seconds instead of after the full shot, services can target a specific room on multi-room installs, and a generated dashboard covers however many zones you actually have. Fixes a crash that broke every service call on current Home Assistant versions.
๐ง Technical notes โ integration 2.12.0.
- Fix (HA compat): replaced the removed
hass.helpers.template.now()withhomeassistant.util.dt.now()in all six service handlers โtransition_phase,execute_irrigation_shot,custom_shot,set_manual_overrideand friends crashed withAttributeErroron current HA cores (#15). - Multi-room services: every service accepts an optional
roomslug (documented inservices.yaml); entity targets and computed sensors resolve the room's prefix instead of hardcoding the default room's ids. An unknown room slug raises instead of silently steering the default room (#16). Event payloads gain aroomkey (additive). - Recipes:
apply_recipe/save_reciperaiseHomeAssistantErrorwhen the recipe store is unavailable instead of silently doing nothing (#27). - Health / Repairs: the kill-switch and heartbeat checks resolve per room โ preferring the
enable_flagthe running engine publishes on its heartbeat, then the room'sengine_configdescriptor โ so custom kill switches and additional rooms are all monitored, with no false "kill switch missing" cards (#22). - Config flow:
.env/config.yamlreads moved off the event loop viaasync_add_executor_job(#25). - Metadata / hygiene: dead
SERVICE_*constants replaced with the real service names;iot_classcorrected tocalculated(#31)..gitignoreno longer ignores the shippedcustom_components/andwww/directories (new files were silently invisible to git); five dashboard files that ship via the publish script are now actually tracked (#29). - Dashboards:
scripts/build_lovelace.pyis zone-count-driven (1โ24, no hardcoded[1,2,3]), per-room viaCROP_STEERING_PREFIX, and free of facility entity ids; the phantominput_select.growth_phase/nutrient_phase"selectors disagree" false alarm is gone from the generator and the committed sample. The staticdashboards/andpackages/files are marked as F2 facility examples (#23, #24). - CI: the add-on image is built on every PR; the vendored engine copy is diffed against the
source package; repo-hygiene gate; the "Full Installation Test" now runs the real test suites
instead of echoing success (#17, #18, #19). New dependency-free fake-HA harness drives the real
service handlers, health checks, and add-on controller (
tests/,addons/f2_control/tests/).
Crop Steering add-on 0.11.0 (paired with this release)#
๐ฑ In plain English. The engine no longer carries the original facility inside it. It learns your pump and valves from the Crop Steering integration, holds safe (with a clear message) if nothing is mapped yet, reacts to the kill switch during a shot, notices new rooms without a restart, and tells you loudly if a setting entity has gone missing instead of quietly running on built-in defaults.
โ ๏ธ Upgrade notes (read before Rebuild).
- Update the integration first. The engine reads the default room's pump/mainline/valves from
sensor.crop_steering_engine_config. Rebuilding the add-on while running an old integration that doesn't publish that sensor leaves the default room held safe (no watering) until the integration is updated โ the engine re-checks every 5 minutes and resumes on its own. notify_servicenow defaults to empty. Installs that explicitly set it (F2 does) are unchanged; unset = persistent notifications only, no mobile push.- Dosing/fill/flush holds moved to the
hold_entitiesoption (empty default). The four previously hardcoded F2 entities (input_boolean.nutrient_dosing_active,input_boolean.f2_fill_mode,input_boolean.f2_flush_mode,switch.tank_filling) are no longer checked automatically โ F2 must add them tohold_entitiesin the add-on Configuration or its tank-dosing holds go dark after the rebuild (same pattern as the v0.8.0 feed-sensor change).
๐ง Technical notes.
- Default-room hardware from the integration descriptor; hardcoded F2 pump/valve fallback removed;
unmapped room โ
_blocked()returns "no hardware mapped" and_safe_off()tolerates it (#14). _wait_shot(): shots delivered in โค2 s slices re-checking the kill switch and the zone's manual override; aborts run the normal close sequence and count delivered volume proportionally (#20).- Periodic
_rediscover()(default 300 s,rediscover_secondsoption): late/UI-added rooms join fail-safe OFF; an unmapped default room picks up its hardware once the integration is up (#26). - Missing-setpoint surfacing: a setpoint entity absent (per-zone AND global) for โฅ3 consecutive
loops raises one rate-limited alert + a vitals line; engine-only knobs
(
min_floor_drown_ceiling) are exempt by design (#27). - Timezone hardening:
tzdatain the image; startup logs the effective local offset and alerts if it disagrees with HA's configured zone (#28). - Heartbeat sensors now publish the room's
enable_flag(consumed by the integration's health checks). Vitals branding viainstance_nameoption;sensor.f2_control_vitalsid unchanged. - Packaging: tracked placeholder
www/public/index.htmlsodocker buildworks from a clean checkout; the publish script still overlays the full dashboard set (#17). Version 0.10.5 โ 0.11.0.
Earlier history (20 entries, Unreleased back to 1.x)
[Unreleased]
Crop Steering add-on 0.10.5#
๐ฑ In plain English. Fixes a serious silent failure: on some installs the engine couldn't find the zone moisture probes at all, so the zones froze in whatever phase they were in (they never even went to overnight dryback at lights-off) and watering fell back to a dumb timer. This happened when a box was first set up under an older version โ Home Assistant keeps the original sensor names forever, and the engine was only looking for the new ones. The engine now finds your probes under either naming, and even if a probe truly dies, the daily light-cycle phases keep moving and you get a repeating phone alert (instead of one easy-to-miss ping) telling you exactly which sensor it wants.
๐ง Technical notes.
- New
Controller._fused_id(): resolves each zone's fused sensor by probing HA for the current id (sensor.crop_steering_<room>vwc_zone_N) then the registry-sticky legacy id (..._zone_N_vwc); caches the hit; re-resolves while neither exists yet (HA still booting); an explicitzones-option override wins._detect_zonescounts zones under either convention. - New
Controller._blind_time_transition(): a blind zone (no readable VWC) still honours the time-based phase forces โ lights-off โ P3, and P3 โ P0 at the new photoperiod including the daily counter/EC-state reset โ so a dead probe can never strand the daily cycle. VWC-driven transitions stay paused while blind. - The blind alert re-fires on the
_alert30-min debounce (was once-per-blindness) and names the exact entity_id being looked for. - Regression-locked by
tests/test_blind_and_fused.py(11 cases: both naming conventions, override, cache/re-resolve semantics, blind P3/P0 forces + counter reset, mid-day hold).
Crop Steering add-on 0.10.4#
๐ฑ In plain English. Dashboard fixes the operator asked for: the Engine Log panel now shows the engine's live decision feed (it was always empty before), the "next" value on the irrigation- frequency card is populated, and (with 0.10.2) the per-zone Volume fed + shots tiles work. Plus a safety improvement: if a valve can't be confirmed closed (e.g. HA drops mid-shot) the engine now raises a CRITICAL alert instead of silently assuming it closed.
๐ง Technical notes.
- Engine Log โ reads
sensor.crop_steering_activity_log(feed attr) instead of the unwritable/local/f2_engine.log; change-gated (also removes the 4s re-render churn โ a lag suspect). - New per-zone
prediction_zone_N_next_irrigation_hourspublished fromdecide()inputs (P2 dry-to- threshold รท dryback rate). _execute_shotclose sequence: check everyturn_off+ HA-aware read-back; alert on failed/unconfirmed close (don't let a dead-HA read mask a stuck-open valve). Shot still counted so the daily cap stays honest. Software can't close a valve when HA is down โ hardware fail-safe is the guarantee (NC valves, pump default-off, independent watchdog).core.py: comment documenting that the MIN-DAILY floor and the daily-volume cap are mutually exclusive by construction (the audit-flagged "floor under-water" case is unreachable โmin_daily_volume <= max_daily_volumeclamp + the floor only fires belowmin). No behaviour change.
Crop Steering add-on 0.10.2#
๐ฑ In plain English. Fixes the per-zone "Volume fed vs daily cap" and "Irrigation frequency" tiles showing blank (โ). The engine now publishes each zone's litres-fed-today + shots-today.
๐ง Technical notes. _publish_status now emits sensor.crop_steering_<room>_zone_N_daily_water_app
(L, from state.daily_vol) and ..._irrigation_count_app (from state.shots) per zone โ the dashboard
already read them; they just weren't being republished. Reset at the P3โP0 lights-on rollover.
Crop Steering add-on 0.10.1#
๐ฑ In plain English. Per-room dashboards. View (and drive) any additional room by adding
?room=<slug> to the dashboard URL โ e.g. f2.html?room=f1 or overview.html?room=f1. A green badge
shows which room you're looking at, and the kill-switch button controls that room's kill switch.
๐ง Technical notes. Two chokepoints in www/f2.html + www/overview.html โ fetchStates aliases
every crop_steering_<slug>_* entity onto the un-prefixed id the UI reads, and svc/callService
rewrites entity_id on writes โ so all ~150 entity references scope to the room without per-id edits.
The kill switch is resolved per room explicitly (switch.crop_steering_<slug>_engine_enabled vs the
default input_boolean.f2_control_enabled). No ?room= โ identity no-op (default room unchanged).
Facility tiles (camera/tank/climate, not crop_steering_-namespaced) stay on the default room.
[2.11.0] - 2026-06-29
๐ฑ In plain English. Multiple rooms are now autonomously steered. Until now you could configure a second grow room (add the integration again), but the engine only ever watered the first one. Now the one add-on drives every room โ each with its own pump/mainline/valves, reservoir pH/EC probes, photoperiod, kill switch and durable state, fully isolated. A new room comes up fail-safe OFF (its own kill switch starts off) so nothing waters until you arm it. Your existing room is completely unchanged.
๐ง Technical notes.
- Integration: each room now publishes a
sensor.crop_steering_<prefix>engine_configwhose attributes carry the room's hardware map (pump / mainline / per-zone valves), kill switch, feed probes and zone count โ the add-on can't read the config entry directly, so this is how it discovers a room. Additional rooms also get a per-room kill switchswitch.crop_steering_<slug>_engine_enabled(default OFF); the default room keepsinput_boolean.f2_control_enabledunchanged. The setup wizard's hardware step now also collects optional per-room source-water EC/pH probes. - Add-on (engine 0.10.0): refactored the controller around a
Roomabstraction;loop_oncenow iterates rooms (_loop_room). Every per-zone entity read/write is namespaced by the room prefix (sensors, numbers, selects, switches, the gates, the published status). Lights, feed gate, kill switch, Vmax tracking and blind-zone handling are all per-room. The default room is built from the add-on options exactly as before (prefix "" โ byte-identical entity ids), and additional rooms are discovered from the published descriptors. Shots still serialise across rooms (one_busylock). - State:
/data/state.jsonis now nested by room slug; an old flat single-room file loads transparently as thedefaultroom (and named rooms seed fresh) โ proven bytests/test_state_migration.py. Newbuild_engine_config(pure) +tests/test_room.pycases. - manifest 2.10.0 โ 2.11.0. (Per-room dashboard scoping โ
?room=โ is still the remaining roadmap item; the engine and entities are fully multi-room.)
Crop Steering add-on 0.9.1#
๐ฑ In plain English. New logo โ cannabis leaf + a green growth chart and rising arrow, with the Home Assistant and Python marks.
๐ง Technical notes. Rebuilt img/crop-steering-logo.png (README hero) + add-on icon.png (emblem,
squared) / logo.png from the new artwork; add-on 0.9.0 โ 0.9.1. Integration unchanged.
[2.10.0] - 2026-06-29
๐ฑ In plain English. Two of the genuinely useful ideas from the retired engine are back โ rebuilt
lean and safe. Named-stage recipes: pick Veg / Transition / Bulk / Ripen from a dropdown and
it writes that stage's setpoints (VWC targets, dryback, EC targets, EC ceiling, shot size) into your
zones in one move โ no more hand-entering every number per stage. Vmax advisory: the engine now
watches each zone's morning wet-up and publishes the field-capacity ceiling it actually reaches
(sensor.crop_steering_zone_N_vmax_detected, with a confidence) so you can steer off the substrate's
real ceiling โ advisory only, it changes nothing on its own.
๐ง Technical notes.
- Recipes (integration): new
select.crop_steering_recipe_stage(Veg/Transition/Bulk/Ripen/Custom) โ selecting a stage applies its row. The recipe table is stored server-side per room via the HAStorehelper (.storage/crop_steering_recipes_<entry>), not entities (no per-stage sprawl) and not browser localStorage. Read it from the select's attributes; author it with the newcrop_steering.save_recipeservice; apply withcrop_steering.apply_recipe. Applying writes the existingnumber.crop_steering_[zone_N_]<param>entities โ engine unchanged, works with the add-on off, every value clamped by the number's min/max and still gated by the kill switch. Curated param set (8):p1_target_vwc,p2_vwc_threshold,generative_dryback_target,p0_dryback_drop_percent,ec_target_gen_p1/p2,maximum_ec,p2_shot_size. Newrecipe.py(+build_targetspure helper),tests/test_recipe.py. - Vmax advisory (engine/add-on 0.9.0): new pure
detect_vmax()incrop-steering-engine(P1 wet-up plateau detection โ marginal-uptake collapse โ ceiling + confidence; unit-tested,tests/test_vmax.py). The add-on tracks the per-zone P1 wet-up (non-persisted) and publishessensor.crop_steering_zone_N_vmax_detected. Does not touch any irrigation decision. - Re-synced the add-on's vendored engine copy with the canonical
crop-steering-engine/src(had drifted by a docstring). manifest 2.9.4 โ 2.10.0.
[2.9.4] - 2026-06-29
๐ฑ In plain English. Honesty + cleanup pass. The retired AppDaemon engine (and its
experimental adaptive / Vmax layer) is removed from the repo โ the f2-control add-on has been
the only engine for a while, and keeping the dead code around made the docs claim things the live
system doesn't do. The README is corrected to match the actual code: the self-tuning "Vmax / adaptive
steering" section is gone (it was never in the live engine), the dashboard tab names and the safety
gate chain are fixed, and overstated claims (a >4 h self-healing watchdog, daily shot-count caps,
?room= dashboard scoping) are reworded to what's real. New SYSTEM_GUIDE.html
(feature list + flowcharts) and docs/DASHBOARDS.md (what's wired, what's
hardcoded to F2, and how to populate the dashboards for your own facility).
๐ง Technical notes.
- Removed
appdaemon/(retired engine + libs + the lean harness),appdaemon.yaml, andpackages/f2_adaptive_steering.yaml(the retired adaptive layer's HA package). - Test coverage preserved: the 28 useful cases from the deleted
test_lean_decide.pywere ported tocrop-steering-engine/tests/test_decide_edge_cases.py(imports the activecrop_steering_engine); engine suite now 39 cases. CI drops the standalone lean-harness step (the engine pytest step already covers it); state-persistence stays covered bytests/test_state_migration.py. - README corrected against the code: deleted the Adaptive/Vmax section; fixed the features table
(sensor fusion credited to the integration, watering watchdog vs the fictional self-healing one,
daily volume cap only, multi-room note); fixed the safety-gate mermaid (field capacity is the P1
ceiling, not a pre-shot gate); fixed the dashboard tab names (Overview ยท Substrate ยท Zones ยท Steering
ยท Analyze ยท Climate ยท Floorplan); removed the non-functional
?room=deep-link; clarified the hardware map is set in the integration wizard, not the add-on Configuration. Zones badge1โ24+โ1โ24. - Scrubbed every remaining AppDaemon reference from docs, CI workflows,
CLAUDE.md,crop-steering-engine, and integration code comments. SYSTEM_GUIDE.htmlrebuilt (self-contained, render-verified) and mirrored towww/;SYSTEM_GUIDE.mdrewritten to match. manifest 2.9.3 โ 2.9.4.
Crop Steering add-on 0.8.3#
๐ฑ In plain English. Uses the original Open Crop Steering logo artwork (leaf + water-drop in a green ring) for the add-on icon and logo, instead of a redrawn version.
๐ง Technical notes. icon.png (cropped circular emblem) + logo.png rebuilt from the source
artwork; version 0.8.2 โ 0.8.3. Integration unchanged (the README hero uses the same artwork).
[2.9.3] - 2026-06-28
๐ฑ In plain English. Branding + docs refresh. New Crop Steering logo (leaf + water-drop in a green ring). The README now explains, clearly: the two parts of the install (the integration vs the add-on, and which does what), how the dashboard works and how to jump between its parts (and run it per room), and how to embed it as a card. The feature list is brought current.
๐ง Technical notes.
- New
img/crop-steering-logo.png(README hero) + add-onicon.png/logo.png. - README: a two-part install table; the wizard step rewritten (entity-picker dropdowns, 1โ24+ zones,
ConfigureโEdit zones, multi-room, SettingsโRepairs); the dashboard section rewritten (served as a
sidebar ingress panel, the tab bar,
?room=deep-links, aniframe/Webpage card, and the Lovelace alternative); new feature rows (multi-room, sidebar dashboards, health checks, configure-once, multi-probe per zone). manifest 2.9.2 โ 2.9.3.
Crop Steering add-on 0.8.2#
๐ฑ In plain English. New logo, matching the project mark.
๐ง Technical notes. New icon.png + logo.png; version 0.8.1 โ 0.8.2.
[2.9.2] - 2026-06-28
๐ฑ In plain English. Fixes the log warnings about EC sensors using "mS/cm which is not a valid unit for device class voltage". The EC sensors were mistakenly tagged as voltage โ they measure conductivity, so the wrong tag is removed. The value and unit (mS/cm) are unchanged.
๐ง Technical notes.
sensor.py: removeddevice_class=SensorDeviceClass.VOLTAGEfrom the EC sensors (configured_avg_ec,ec_zone_N). EC now reports mS/cm with no (incorrect) device class โ no more unit-validation warnings. manifest 2.9.1 โ 2.9.2.
Not a code bug: "the engine ignored my dripper flow rate / drippers per plant" is the old add-on (0.2.0), which reads its own options, not the per-zone integration numbers. Add-on 0.5.0+ reads
number.crop_steering_dripper_flow_rate+ per-zonedrippers_per_plantlive โ update the Crop Steering add-on to 0.8.0.
[2.9.1] - 2026-06-28
๐ฑ In plain English. Bug fixes. The integration no longer spams the log trying to create test helpers (a dead feature), and the Configure (gear) button works again. Also โ there's now a proper GitHub release, so HACS will actually offer the update; earlier versions (2.7โ2.9) were pushed but never released, which is why the new setup wizard hadn't shown up yet.
๐ง Technical notes.
- Removed
_create_test_helpersโ it called the non-existentinput_boolean.create/input_number.createservices (the "Action input_boolean.create not found" spam, 56ร). The simulation helper entities are gone. - OptionsFlow no longer assigns
self.config_entry(read-only in modern HA โ 500 on the Configure dialog); uses an internalself._entry. - Dropped a stale "v2.3.1" startup log line; removed the now-unused import. manifest 2.9.0 โ 2.9.1, and tagged a release so HACS surfaces the UI setup wizard, reconfigure flow, multi-room and Repairs.
[2.9.0] - 2026-06-28
๐ฑ In plain English. Multi-room โ Stage 1. You can now add more than one grow room: go to Devices & Services โ Add Integration โ Crop Steering again, give the room a name, and map its own zones, sensors, pump and setpoints. Each room is completely isolated from the others. Your existing setup is the "default" room and is untouched โ its entities keep exactly the same names. (The engine driving the new rooms comes in the next add-on release; this stage lets you create and wire them.)
๐ง Technical notes.
- Removed the single-instance lock; each config entry is now a room. New
room.py: the default room uses no entity prefix (back-compat), named rooms namespace ascrop_steering_<slug>_*. Entityobject_idin every platform (sensor/number/switch/select/ button) now prependsroom_prefix(entry)โ""for the default room, so existing entity ids are byte-identical. - config_flow:
async_step_roomnames an additional room (slug โ prefix) and routes it through the UI mapping flow;.env/manual default-room paths setroom_prefix="". Test-helper entities are created only for the default room (no collisions). - Health/Repairs are per-room: issue ids suffixed by room slug, fused-sensor checks use the room prefix, kill-switch/engine checks run for the default room only (until the engine is multi-room).
tests/test_room.pylocks "default room stays un-prefixed". manifest 2.8.0 โ 2.9.0.
Crop Steering add-on 0.8.1#
๐ฑ In plain English. Fixes "it ignored my dripper flow rate and drippers per plant." The engine only used those numbers when a per-zone plant count was also set; if plant count was blank it quietly fell back to a generic flow value. Now your dripper flow rate + drippers/plant always drive how long each shot runs (plant count cancels out of that maths anyway).
๐ง Technical notes.
controller.py_zone_flow_lps: dropped theplant_count > 0gate (plant_count cancels against the zone-total substrate in the duration), defaulting it to 1; falls back to theflow_lpsoption only ifdrippers_per_plantordripper_flow_rateis unset. 0.8.0 โ 0.8.1.tests/test_state_migration.py: regression test that dripper settings drive flow at plant_count=0.
Crop Steering add-on 0.8.0#
๐ฑ In plain English. Works out of the box on any system โ no more F2-specific defaults. The source-water pH/EC feed gate is now optional: leave the two sensor fields blank and that gate is simply off (your dosing / tank-fill holds still apply); set them to your reservoir probes to turn it on. The fallback substrate/flow numbers are generic placeholders now, not F2's.
๐ง Technical notes.
config.yaml: new optionalfeed_ec_sensor/feed_ph_sensor(str?, default""); removed the hardcoded F2 atlas/aquaponics probe defaults.substrate_l6 โ 5,flow_lps0.067 โ 0.02 as neutral last-resort fallbacks (real shot sizing still reads the per-zone integration numbers). Version 0.7.0 โ 0.8.0.controller.py: feed sensors read from options; an empty value disables that half of the source-water gate (logged) so a fresh install isn't held by a dead default probe. Tooltips added.- Closes the "genericise F2-specific defaults" debt โ the add-on now works for anyone, not just F2.
Crop Steering add-on 0.7.0#
๐ฑ In plain English. The add-on is renamed F2 Control โ Crop Steering with a new logo, a proper description + feature list, and a changelog you can open from the Info page. The operator dashboards show as a sidebar panel (toggle Show in sidebar on the add-on's Info tab). ("F2" was just one grow room โ this drives any number of zones.)
๐ง Technical notes. config.yaml name/panel_title โ "Crop Steering"; new icon.png +
logo.png (Open Crop Steering mark); DOCS.md (Documentation tab) + add-on CHANGELOG.md
(Info-page Changelog link); version 0.6.0 โ 0.7.0. Slug stays f2_control (kill switch
input_boolean.f2_control_enabled unchanged) so existing installs aren't orphaned.
[2.8.0] - 2026-06-28
๐ฑ In plain English. The integration now watches your setup and flags problems in Settings โ Repairs with a plain-language fix โ e.g. the kill-switch helper is missing, the engine add-on isn't running, a zone has no moisture sensor, or a zone's sensor reads unavailable. No more silent misconfiguration; each card clears itself once you fix it.
๐ง Technical notes.
- New
health.py+ hooks in__init__.py: a read-only check runs 60 s after setup and every 5 min, creating/clearing Home Assistantissue_registryissues โkill_switch_missing(error),engine_offline,zone_no_sensor,fused_sensor_unavailable(warnings) โ each with alearn_more_urlto the Troubleshooting wiki. Timers are cancelled and issues cleared on unload. Never reads/writes hardware. strings.json+translations/en.json: anissuesblock (title + description) for each.- manifest 2.7.0 โ 2.8.0 (README badge + this changelog in sync).
[2.7.0] - 2026-06-27
๐ฑ In plain English. You can now set the whole thing up โ and fix it later โ from the Home Assistant UI with dropdowns, no text file. Pick "Manual UI configuration" at setup and it walks you through choosing each zone's valve and moisture/EC probes (as many as you have per zone), then your pump, main line, lights and substrate facts. Already installed and forgot a sensor? Go to the integration's Configure โ Edit zones & hardware and add or swap sensors, or add a whole zone โ no reinstall. Every field has a help tooltip explaining what it is. This fixes the "I didn't set it up right at the start and there's no way to change it" problem.
๐ง Technical notes.
config_flow.py: the manual path (async_step_zonesโ newasync_step_hardware) now uses HAEntitySelectors โ per zone: valveswitch, VWCsensor(multiple), ECsensor(multiple), plant count; then shared pump/mainline/waste/light + lights hours + substrate volume/dripper/field-capacity/max-EC + optional room sensors + notify. Writes the same config-entry shape as the.envpath (zone_switch+vwc_sensors/ec_sensorslists +vwc_front/backcompat).- OptionsFlow:
async_step_edit_zones(count) โ newasync_step_edit_zones_map(entity remap, prefilled) โ updates the entry and reloads the integration. Replaces the old dead zone submenu. - Multiple probes per zone are now selectable and flow through to the integration's
_average_sensor_valuesfusion (median/average + the engine's outlier rejection). strings.json+ newtranslations/en.json:data_descriptiontooltips on every field of every config and options step (zones 1โ24 + all hardware), plus menu labels.- Bumped integration
manifest.json2.6.0 โ 2.7.0 (README badge + this changelog in sync).
f2-control add-on 0.6.0#
๐ฑ In plain English. Configure once. The engine now reads your lights hours and zone count straight from the integration, so you set them in the Crop Steering UI and the add-on just follows โ no more keeping the add-on options and the integration in sync (the class of bug behind the "lights were 2 hours off" problem). The add-on options for these become fallbacks used only if the integration isn't reachable.
๐ง Technical notes.
controller.py: zone count is auto-detected fromsensor.crop_steering_vwc_zone_N(_detect_zones, fallback to thenum_zonesoption). Lights are read live each loop fromnumber.crop_steering_lights_on_hour/_off_hour(_refresh_lights), fallback to the add-on option; the startup log prints the resolved hours + source, and alerts once if the integration value disagrees with the legacy option.- config.yaml 0.5.0 โ 0.6.0;
num_zones/lights_*options relabelled as fallbacks. - Operator note (live grow): the engine now reads
number.crop_steering_lights_on_hour/_off_hour. Set those on the dashboard to your real hours before arming โ the add-on log showsconfig: lights HH:00-HH:00 (source: โฆ); confirm it's right. If the integration entity is missing it transparently falls back to the add-on option. - Verified:
docker build+ run against the HA base image โ engine boots and logsconfig: lights 10:00-22:00 (source: add-on option โฆ); no crash; state tests 5/5.
f2-control add-on 0.5.0#
๐ฑ In plain English. The engine now reads each zone's sensors from the integration's fused per-zone reading, which averages every probe you've mapped to that zone. So you can add as many moisture/EC probes per zone as you like in the Crop Steering UI and the engine uses all of them (averaged, outliers rejected) โ no add-on editing, and it works for any number of zones.
๐ง Technical notes.
controller.py: the per-zone sensor map now defaults tosensor.crop_steering_vwc_zone_N/sensor.crop_steering_ec_zone_N(the integration's_average_sensor_valuesfusion) fornum_zoneszones, instead of hardcoded raw F2 probes. An explicitzonesoption still overrides. Removes the F2-specific zone-sensor hardcoding.config.yaml: newnum_zonesoption (default 3) + schema; version 0.4.0 โ 0.5.0.translations/en.yaml:num_zoneshelp text.- Operator note (live grow): on Rebuild, confirm
sensor.crop_steering_vwc_zone_1reads the same as your raw probe before arming โ a single-source fused sensor equals the raw value. If a fused sensor is missing the zone holds (no actuation) rather than mis-steering.
f2-control add-on 0.4.0#
๐ฑ In plain English. The dashboards now come inside the add-on โ no more copying
f2.html to /config/www over Samba or SSH. After you install the add-on, F2 Crop
Steering appears in your Home Assistant sidebar; open it and you get a Live/Demo chooser.
Live reads your real sensors using your existing HA login (nothing to paste, no URL to
edit โ it works in the mobile app), and Demo shows mock data so you can look around
safely. Thanks to @ChillingSilence (Chill-Division) for the idea (PR #1).
๐ง Technical notes.
config.yaml:ingress: true,ingress_port: 8420,panel_icon: mdi:sprout,panel_title: F2 Crop Steering; version 0.3.0 โ 0.4.0.Dockerfile:apk add nginx; serve/www/publicon 8420;sedstrips CRLF fromrun.sh(a Windows-checkout shebang issue that otherwise breaks the s6 launch).run.sh: starts nginx (daemonizes) thenexecs the controller as the foreground process, so its SIGTERM safe-valve-off still fires on stop. No s6 service files โ avoids the s6-overlay v2/v3services.dambiguity; nginx dying never touches the engine.nginx.conf: static, read-only, gz, logs to stdout; reachable only through HA ingress (noports:mapping โ not exposed to the LAN).web-index.html: the in-panel Live/Demo landing. Live usesgetToken()โlocalStorage.hassTokens(same origin under ingress) with a one-time token-paste fallback.- Single source of truth:
scripts/publish_addon.shassembles the add-on web root from the repowww/at publish time (no duplicate dashboards committed in the monorepo). - Verified:
docker buildagainstghcr.io/home-assistant/amd64-base-python:3.12-alpine3.20- run โ engine boots (
[f2-control] starting โฆ loop 60s) and nginx serves the dashboards (landing/f2.html/overview 200, unknown path 404). Live-data-through-ingress is the operator's on-box check.
- run โ engine boots (
f2-control add-on 0.3.0#
๐ฑ In plain English. The add-on now has a proper name (โF2 Crop Steeringโ) and a custom
icon in the Add-on Store, and every setting on its Configuration page has a help description
(what notify_service, the lights hours, the kill switch, etc. actually do). Under the hood:
the add-on safely remembers each zone's state across restarts and updates โ so upgrading never
wipes your progress or makes you set things up again โ and there are now automated tests that
guarantee that stays true.
๐ง Technical notes.
- Add-on UX:
config.yamlname: F2 Crop Steering, version 0.2.0 โ 0.3.0; addedicon.png+logo.png; addedtranslations/en.yamlwithconfiguration:name/description for all eight options (shown as field help in the Supervisor UI). - Documented the persistence + compatibility contract in
CLAUDE.md(Compatibility & data):/data/state.jsonis non-ephemeral;_load_statetolerates missing/old/partial state; options read with defaults; stay generic (flagged the F2-specific feed-sensor /substrate_l/flow_lpsdefaults as known debt). - Tests:
tests/test_state_migration.py(in-place upgrade โ oldstate.jsonloads: missing file, corrupt JSON, missing/unknown keys, bad timestamp, new zone seeded, saveโload round-trip) andtests/test_version_consistency.py(manifest == CHANGELOG == README badge). AddedTESTING.mdandtests/run_ci.sh(local CI mirror). Both new suites green; fulltests/34/34.
[2.6.0] - 2026-06-27
๐ฑ In plain English#
- The Analyze page is rebuilt and actually useful. It now shows your room at a glance: a 24-hour timeline of when the lights, pump and each zone's valves ran; simple graphs of each zone's moisture and salt level; your climate (temperature, humidity, VPD, COโ, light); a "what's holding you back" ranking that points at the single worst factor; and your feed water's pH and EC. Every card has a plain note on what good and bad look like.
- The dashboard no longer makes your mouse stutter. The 3D room view used to render non-stop in the background and bog down the whole computer โ now it only draws when you're looking at it.
- The pump short-cycling is fixed. It was firing tiny bursts because it sized each watering for one plant's worth of substrate instead of the whole row, so the moisture never rose and it kept re-firing. Waterings are now the right length.
- A guaranteed minimum daily water per plant (optional safety). You can set how many millilitres each plant must get per day; the system delivers it early in the day regardless of what the moisture probe says โ so a faulty or badly-placed sensor can't quietly starve a plant. Off until you set a number.
- A safety limit on how long any single watering can run, so a wrong setting can't flood the room.
- Smaller fixes to the salt-steering control and the feed-water safety checks.
To get these on your live system: the engine runs as a Home Assistant add-on that bundles its code when it's built โ so after updating the files you must Rebuild the add-on (Add-ons โ F2 Control โ โฎ โ Rebuild), not just restart it. The dashboards update on a normal browser hard-refresh.
๐ง Technical notes#
Added#
- Comprehensive Analyze tab (
www/f2.html), all live off real F2 entities. Seven full-width cards: a 24 h equipment-state timeline (lights / pump / mainline / zone valves / recirc / CO2 / dehumidifier), per-zone VWC dryback + pore-EC-vs-target sparklines, water + substrate-temp + phase per zone, climate drivers (air temp / RH / VPD / CO2 / PPFD / DLI), a Liebig limiting-factor ranking (the bottleneck flagged first), and feed pH/EC gauges vs their bands. No Chart.js โ inline SVG sparklines + CSS bars, each card with what / good / bad help text. - Minimum daily water floor โ per-plant safety.
input_number.crop_steering_zone_N_min_daily_ml_per_plant(mL/plant/day; รplant_countโ a zone-litres floor). Guaranteed, front-stacked and sensor-independent: every plant gets its minimum, delivered from lights-on as fast as the anti-short-cycle spacing allows, regardless of the VWC probe (a lying/dead probe can't suppress it). The only VWC gate is a hard anti-drown ceiling (drown_ceiling, default 90); feed-water + dosing safety still apply. Clamped <max_daily_volume. 0 = off. (addons/f2_control/f2_min_daily_package.yaml) - Hard max shot-duration cap (flood guard).
number.crop_steering_max_shot_duration(default 900 s) clamps the computed shot length and alerts if the raw value exceeds the cap (a substrate/flow misconfig) โ the upper bound to match the per-zone minimum-water floor's lower bound.
Changed#
- Analyze rebuilt lightweight. Replaced the collapsing Chart.js auto-fit grid (broken widths) with full-width stacked cards; removes all Chart.js cost on that view.
www/overview.htmlโ per-zone daily water now readssensor.crop_steering_zone_N_daily_water_usage(the live entity; the old name was blank), and the Pore-EC target resolves by the zone's veg/gen steering mode instead of hardcoding generative.
Fixed#
- Dashboard mouse-glitch / desktop stutter. The 3D floorplan (separate WebGL app,
www/floorplan, built fromthree-playground) ran a perpetualsetAnimationLoop, rendering a shadow pass at the monitor's native refresh (60โ144 Hz) forever and saturating the GPU. Converted to render-on-demand (draw only on orbit / zoom / live-data change / door-swing; OrbitControls damping off; shadow baked once at 1024). Idle = 0 animation frames (verified). f2.html30 s churn. Tune-editor reseed gated to the active view (was rebuilding its heavy SVG from sensor noise every tick while hidden); the dashboard trend chart skips its re-bucketize when history is unchanged; chart build-animations off; the 30 s poll + chart pipeline pause while the browser tab is backgrounded; the engine-log status dot only pulses when the panel is open.- EC steering double-correction. Removed a second P2 EC-steer nudge in the pure
decide()โ the IO shell already accumulates and bakes the EC offset into the P2 threshold;_paramsnow clamps that offset-threshold to the engine's safe band. - PID cross-photoperiod windup. The EC-PID integral + previous-error reset on the P0 transition.
- Shot-sizing short-cycle. Shot duration divided per-plant substrate by zone-total flow
(
plant_count ร drippers ร flow_rate), so per-plant 6 L blocks ร 36 plants computed shots ~36ร too short (~14 s instead of ~324 s for a 6 % shot) โ VWC never rose โ the pump short-cycled._substrate_lnow returns the zone total (per-plant block ร plant_count) to match the zone-total flow; entersubstrate_volumePER-PLANT.
Deploy note. The f2-control add-on bakes its code into the image at build time (
Dockerfile COPY), so the engine changes in this release require an add-on Rebuild (Supervisor โ F2 Control โ โฎ โ Rebuild), not just a restart, to take effect. (Interim: set the add-on Configuration optionssubstrate_l= row-total litres andflow_lps= zone flow, then Save, to correct shot length on the running build without a rebuild.)
[2.5.0] - 2026-06-27
๐ฑ In plain English#
- A phone-friendly control page (
overview.html): per-zone moisture, EC and targets, room climate, and the pump / light / dosing controls all on one mobile screen. - Optional precision salt-steering (a PID controller) for substrate EC โ more accurate than the simple version, off by default.
- Waterings are sized from your real hardware (substrate size + dripper flow) instead of a fixed guess.
- It won't water with bad feed water โ now checks pH as well as EC, and fails safe if a probe dies.
- No more pump machine-gunning on salt corrections โ those waterings now wait a minimum gap so they flush properly instead of stacking salt.
๐ง Technical notes#
Added#
- Mobile control surface โ
www/overview.html. A lightweight, phone-first one-pager with three tabs: Steer (per-zone VWC + target / pore-EC + target / substrate temp / phase / last shot / daily L; room temp, VPD, CO2, PPFD, DLI, total daily water), Controls (main pump, flush/fill modes, grow lights, per-zone enable), and Dosing (the veg-room peristaltic pumps โ Cleanse/Core/Bloom/Balance โ feeding F2). Vanilla JS, one 30 s poll,?demomode, confirmed actuation. - PID EC loop (engine
ec_pid(), flag-gated, OFF by default). A real Kp/Ki/Kd controller with integral anti-windup and output clamped to ยฑ20 % of the P2 threshold, as an opt-in upgrade to the stepped EC-offset. Enable withinput_boolean.crop_steering_ec_pid_enabled; gains tunable viainput_number.crop_steering_ec_pid_{kp,ki,kd}(addons/f2_control/f2_ec_pid_package.yaml).
Changed#
- Shot sizing reads the LIVE config. The f2-control add-on now computes shot duration from the real
substrate volume + per-zone flow (
plant_count ร drippers_per_plant ร dripper_flow_rate) instead of hardcoded options โ a mis-set substrate (6 L vs the real 9 L) fired shots ~โ too short, so VWC never rose and the pump machine-gunned. Fixed. .envis now authoritative for setpoints โ number entities seed their initial value from the parsedcrop_steering.envparams, falling back toDEFAULT_VALUES.
Fixed#
- pH half of the source-water gate โ f2-control now gates on feed pH (
irrigation_ph_min/max, with last-good grace + fail-closed on a dead probe), not just EC. Bad-pH feed no longer waters. - Fail-closed hardware writes โ
ha_callreturns False on non-2xx; a shot aborts (cuts hardware, alerts, does NOT advance shot/daily/last_shot counters) if any pump/mainline/valve command errors. - P2 EC-correction short-cycle โ flush/dilute/rescue shots respect a minimum interval
(
p2_min_interval_min, default 10 min); no-runoff nibbles can no longer stack EC + machine-gun the pump. f2.htmllag โ only the active tab rebuilds per 30 s tick, and backdrop-filter blur removed from the ~78 glass cards (fixes cursor/scroll jank).services.execute_irrigation_shotโ zone ids coerced to int (azone: 1call no longer rejects against a"1"key after a config-entry reload).
Removed#
- The retired AppDaemon engine is no longer bundled in the release artifact (kept in-repo as a manual rollback only).
[2.4.0] - 2026-06-26
Added โ f2-control standalone add-on + extracted crop-steering-engine (AppDaemon retired)#
addons/f2_control/โ a standalone Home Assistant add-on that replaces the AppDaemon engine as the autonomous coordinator. One synchronous Python process, plain REST polling of HA (no asyncio coroutine trap), hardware shots with valve-close readback, republishes the fullsensor.crop_steering_*status surface, sends 30-minute vitals to the operator's phone, and is gated by a hard kill switchinput_boolean.f2_control_enabled(OFF = reads/computes but never actuates). Installs from the local add-on store; no apps.yaml, no scipy/numpy.crop-steering-engine/โ the pure decision core extracted as a standalone,pip-installable Python package (src-layout, offline unit tests, ruff/mypy/pytest CI). No HA, no I/O โdecide()over two dataclasses, so the same engine runs identically inside the add-on, AppDaemon, or a test. The add-on vendors a copy for a self-contained Docker build.www/overview.htmlโ a new lightweight, mobile-first one-page vitals/overview (per-zone VWC/EC/phase/last-shot/daily water, system + feed status, kill-switch toggle). Vanilla JS, one 30-second poll,?demostandalone mode.
Changed#
www/f2.htmlperformance โ the 30-second refresh now only rebuilds the active tab's data (off-screen tabs no longer re-render every tick) and the camera frame only refetches while its tile is visible. Fixes the lag/freeze.www/irrigation-manual.htmlโ restyled to the F2 white-paper dark theme (Home Assistant Roboto font) and updated for the f2-control add-on architecture (kill switch, per-zone EC targets, new safety features).
Fixed#
- Engine P2 short-cycle / EC stacking โ P2 EC-correction shots (flush / dilute / rescue) now respect a minimum
interval (
p2_min_interval_min, default 10 min). Small no-runoff shots fired every tick were stacking pore EC instead of diluting it, short-cycling the pump. VWC top-ups stay ungated (self-limiting).
Security#
- Scrubbed operator PII (real names, site label) from tracked config + docs before public release.
Added โ adaptive steering: self-tuning P1/P2 and predictive P3 (optional, off by default)#
- New drop-in engine module
appdaemon/apps/crop_steering/adaptive_steering.py, gated byinput_boolean.f2_adaptive_steering_enabled. Wires intomaster_crop_steering_app.pyvia four small hooks (import;tick()at the end of_update_zone_vwc_capacity; per-zone rate in_get_zone_dryback_rate; buffer-safe cap in_should_zone_start_p3). Every setpoint write is clamped and confidence-gated; nothing runs unless the switch is on. - Per-zone P1
Vmaxdetection โ votes four independent saturation signals (marginal-uptake collapse, peak plateau, pore-EC runoff, saturating-curve fit) to detect each zone's true field-capacity ceiling during the morning ramp, with a confidence score. Published assensor.crop_steering_zone_X_vmax_detected. - Dryback-derived P2 โ the P2 trigger is set to
Vmax ร (1 โ dryback_target%)per zone/mode, so each zone dries back the correct % from its own measured ceiling; the EC-stacker trims around it. - Multi-day P1 ramp โ the P1 target climbs toward the detected
Vmax(capped below field capacity) a configurable amount per day, instead of a static hand-tuned number. - Predictive per-zone overnight (P3) cutoff โ measures each zone's own overnight dryback rate and
feeds it into the engine's P3-start timing (previously a single fused room rate); caps the overnight
target dryback so the predicted lights-on VWC stays above the P3 emergency floor + buffer, landing on
the dryback target without firing overnight emergency shots. Forecast published as
sensor.crop_steering_zone_X_p3_prediction. - Control surface in
packages/f2_adaptive_steering.yaml(enable switch +f2_vmax_confidence_min,f2_p1_climb_rate_per_day,f2_p1_margin).
Added โ manual pump modes (flush / fill)#
packages/f2_pump_modes.yaml:input_boolean.f2_flush_modeandf2_fill_modehand the pump to the operator (hose-flood each plant, or tank fill/dosing). While either is on the engine pauses its own shots and the hardware watchdog is exempted; a 30-minute reminder notification fires so the mode is never left on by accident.
Added โ unified operator console#
www/crop_steering.html: one dependency-free, responsive dashboard (replaces separate desktop/mobile pages). One-glance verdict (ALL GOOD / WATCH / ACT NOW) with a 7-point system-health checklist; word-threshold per-zone status (VWCDRY/LOW/GOOD/FULL/WET, ECLOW/ON-TARGET/HIGH/OVER, dryback); an issues side-drawer with persisted first-seen timestamps and durations; expandable VWC+EC and per-zone trace graphs; per-field coaching tooltips with recommended values; clickable pump/valve toggles; and a feed-lockout panel that names the exact gate blocking a low-VWC zone.
Fixed โ overnight P3 timing and dashboard correctness#
- Per-zone overnight dryback rate replaces the single shared/fused rate that previously timed every zone's P3 cutoff off the room average.
- Buffer-safe overnight cap stops a zone drying past the P3 emergency floor โ no spurious overnight emergency shots.
- Phase-aware floor (dashboard): a zone in P3 is judged against the P3 emergency floor, not the P2 maintenance floor, so normal generative night dryback no longer false-alarms as "under-watered".
- Trust verdict keys off fusion confidence, not the flaky
sensor_healthmetric โ kills a permanent false "data untrusted" state. - Alert debounce + real timestamps: noisy alerts must persist two cycles before showing and carry a persisted first-seen time, so the alert list stops being a graveyard of one-tick blips.
Added โ autonomous EC-stacking via P2 dryback (engine closed loop)#
- New
_ec_stack_drybackper-zone loop in the AppDaemon engine. In P2 it modulates each zone'snumber.crop_steering_zone_X_p2_vwc_thresholdto drive smoothed pore-EC toward that zone's phase target (โฆ_ec_target_{veg|gen}_p2): deeper dryback concentrates salts (EC stacks up), and a pore-EC drop is read as runoff and deepens the dryback further. EWMA-smoothed, 30-min cooldown, bounded by the P3 emergency floor and themin(p1_target, adaptive-cap)ceiling, gated byswitch.crop_steering_ec_stacking_enabled. It is the single owner of the P2 dryback lever (the F2 heartbeat oversees it, no longer tunes it).
Fixed โ engine stability & timing#
- EC sensor-fusion throttle:
_on_ec_sensor_updatenow throttles the heavy fusion + entity writes (10 s per probe, non-blocking lock) so high-frequency EC ticks can no longer starve the AppDaemon worker thread / back up the queue (which had been false-firing the hardware watchdog mid-shot). The critical-EC safety check still runs on every tick. - Daily counters reset at lights-on, not on restart:
_load_persistent_stateis now photoperiod-aware โ the daily water/shot totals reset at the P3โP0 lights-on boundary, so a mid-day AppDaemon restart no longer zeroes the running day. - Manual phase pin holds across restarts: the per-zone
input_select.crop_steering_zone_X_phase_controlis re-read every engine cycle and re-asserted, so a pinned phase survives a restart instead of snapping back to the P2 default. - Stuck-low / not-absorbing alert: a zone fed โฅ2 shots whose VWC stays flat below its P2 floor (channelling / probe out of the wet core / dried rockwool cube) is flagged instead of being chased with more water.
Added โ operator dashboards#
- AROYA-style multi-zone VWC + EC overlay chart (dual-axis, scrubber, 24 h / 3 d / 1 w) on the desktop and mobile control dashboards; full per-entity Control surface and a dark operator-console re-skin.
Experimental โ branch feature/dial-ppfd-rehydration (NOT on main)#
- 1โ10 crop-steering dial (
input_number.crop_steering_steering_dial) that blends the EC-stack target from vegetativeโgenerative. PPFDโsupport coupling and a dried-cube rehydration mode are planned on the same branch.
Changed โ consolidated to a single deployed engine#
The repo now tracks exactly what runs in production: one AppDaemon engine
(master_crop_steering_app.py โ 4-phase crop-steering irrigation) plus the HA
integration. README, CLAUDE.md and docs/SYSTEM_OVERVIEW.md rewritten to match.
Removed โ undeployed experimental layers#
The intelligence/ subsystem (RootSense substrate AI + ClimateSense climate
control) was never deployed and has been removed from main, along with its tests,
the F1/Green Wave legacyag dashboards, the docs/upgrade/ planning docs, and the
lean crop_steering_v2 controller. All recoverable from the
archive/pre-doc-cleanup-2026-06 tag.
[Historical] - 3.0.0-dev "RootSense" (experimental, never deployed)
Added (HVAC mode-switching hardened + LLM advisor Phase L0)#
HVAC mode-switching (operator request: explicit dispatch wiring)#
propose_hvac_off()helper for explicit HVAC shutdown โ used by the watchdog when the unit is in heat mode during a high-temp emergency (it's actively making things worse) and available to a futurecrop_steering.hvac_force_offservice.- Watchdog now receives
hvac_modeand force-emitsHVAC_MODE=offduring a temp emergency only when the unit is currently heating. When cooling, it leaves the unit running โ we want max cooling under emergency. - Dispatch path refactored: pure
action_to_service_call(action)helper translates every Action kind to (HA-service-path, kwargs). The AppDaemon adapter is now a one-liner around it. Tested for all 5 ActionKind variants including all 6 validhvac_modevalues (off/cool/heat/heat_cool/dry/fan_only) โ confirms the IR-blaster template (better_thermostat โ SmartIR) gets the right service. - 4 new control-loop tests covering the watchdog HVAC-off rule, the
propose_hvac_offhelper, and the dispatch translator.
LLM advisor โ Phase L0 (report builder, NO LLM calls)#
- New
appdaemon/apps/crop_steering/intelligence/llm/package. report_builder.pyโRootSenseReportBuilderAppDaemon app builds a compact JSON snapshot every 15 min. The schema mirrors the design inLLM_HEALTHCHECK_PLAN.md:- top-level: ts, phase, intent, recipe_phase, recipe_day, climate, substrate (per-zone), deltas_15m_ago, active_anomalies, triage, estimated_tokens.
- climate: temp/rh/vpd/leaf_vpd/co2 with
_target+_statusclassification (on_target / near_target / off_target). - triage tag: anomaly:* > drift:* > heartbeat > ok (local rule engine, no LLM).
- Pure helpers:
classify_status(),compute_deltas(),compute_triage(),estimate_tokens()โ all unit-tested in isolation. - Published as:
sensor.crop_steering_rootsense_report_latest(state = triage tag, attributes = full payload).sensor.crop_steering_rootsense_report_size_tokens(token estimate).- Bus event
report.ready. - HA event
crop_steering_rootsense_report.
- Switch
switch.crop_steering_intelligence_llm_report_enabled(default OFF) gates the whole pillar. - 21 new unit tests covering classify_status edge cases, delta threshold + nested-substrate walking, triage precedence (anomaly > drift > heartbeat > ok), estimate_tokens proportionality, a realistic full-payload fits-under-500-tokens guard, build_report integration against synthetic state with day/night target switching, anomaly inclusion, drift triage, delta vs prior history, and a guard test that asserts the module imports no LLM client (anthropic / openai / requests / aiohttp absent โ by construction the module CANNOT call an LLM).
- Also fixed a silently-dropped field:
SensorMapnow has explicitleaf_temp(was being filtered out by the YAML loader because the dataclass field didn't exist).
Added (Dashboard wiring)#
- New "LLM Advisor" view in
dashboards/legacyag/30_intelligence.yaml: pillar status, latest triage tag, full report payload as syntax-highlighted markdown, 7-day token-size + triage-history graphs.
Changed (Recorder includes)#
packages/rootsense/00_recorder.yamlextended forsensor.climate_leaf_vpd_kpa,sensor.climate_derived_rh_target_pct, the L0 report sensors, and the LLM-report-enable switch.
Tests#
- Total intelligence tests: 121 (was 95).
- Full suite: 154 passing.
Changed (ClimateSense โ layered control refactor + leaf VPD supervisory variable)#
The monolithic control.py (~290 lines) is replaced with a layered
package mirroring commercial-BMS architecture:
intelligence/climate/
leaf_vpd.py โ pure math (Tetens + RH inversion)
control/
actions.py โ Action / ActionKind dataclasses
hvac.py โ bang-bang + calibration + mode-switch + IR refresh
dehumidifier.py โ lead-lag staging + rotation + demand persistence
humidifier.py โ bang-bang with min-off
co2.py โ pulse cadence + lights-on lead time + adaptive
exhaust.py โ emergency + scheduled refresh + watchdog
coordinator.py โ cross-actuator conflict resolution
watchdog.py โ Tier 4 safety (sensor staleness, runaway, emergency)
app.py โ thin AppDaemon adapter
Added (corrected for F1 reality)#
- Leaf VPD as the supervisory variable. The recipe declares
leaf_vpd_kpaper phase; the control layer reads current leaf temp- air temp + air RH and inverts the Tetens equation to derive the
required RH to hit target leaf VPD. The dehu/humid loops chase the
derived RH, not the recipe's
day_rh_pct(kept as fallback for installs without a leaf-temp sensor).
- air temp + air RH and inverts the Tetens equation to derive the
required RH to hit target leaf VPD. The dehu/humid loops chase the
derived RH, not the recipe's
- 22 unit tests covering Tetens math against published tables, round-trip property tests on the RH inversion, and 6 cannabis-phase envelope checks (transplant through ripening) where solved RH must fall within published cultivation chart bands.
Added (control patterns from commercial cultivation HVAC)#
- HVAC mode-switching with cooldown โ controller never flips
coolโheat more often than
mode_change_cooldown_min(default 30 min). IR-blaster insurance: re-issue last setpoint everyrefresh_interval_minto recover from lost IR commands. - Lead-lag dehumidifier rotation. F1 has 2 dehu units (4 relays as
contactor pairs). Lead engages first on demand persistence; lag only
joins if RH stays high
stage_persistence_minafter that. Reverse order on release (lag off first). Lead/lag swap every 7 days for wear leveling. - Demand persistence โ every actuator requires the deviation to last 3-5 min before staging on; prevents reacting to door-open spikes.
- ACโdehu cooperation โ coordinator defers staging the LAG dehu while AC is already cooling AND temp is above target (AC cooling is already condensing water out via condensation).
- Adaptive COโ pulse cadence โ extends/shortens pulse_off based on observed ฮCOโ during the previous rest period; clamped to [pulse_off_min_seconds, pulse_off_max_seconds].
- Lights-on lead time for COโ โ solenoid won't inject until lights
have been on for
lights_on_lead_min(default 30 min) so stomata are open and COโ uptake is real. - Tier 4 watchdog โ runs every tick. Flags stale sensors (no update
in 90 s), force-closes any actuator that exceeds its
actuator_max_runtime_min, and re-asserts emergency exhaust + COโ cutoff regardless of what other layers are doing. - Exhaust controller โ emergency-only by default (room is sealed). Triggers on temp > 32 ยฐC OR COโ > 1800 ppm; releases when both are back in safe range. Optional scheduled refresh window.
- Severity-tiered actions โ every Action carries a severity (normal / safety / emergency). Coordinator drops normal-severity proposals when a critical anomaly is active; safety/emergency always emit.
Added (operator-facing docs)#
docs/upgrade/SEQUENCE_OF_OPERATIONS.mdโ the operator-readable contract describing exactly what every actuator does, when, and why. Sections cover authority hierarchy, sensors, supervisory variable, per-actuator sequences, cross-actuator rules, failure modes, and loss-of-comms safe state.docs/upgrade/CONTROL_THEORY.mdโ design rationale. Why bang-bang not PID, why leaf VPD not air RH, why hardware calibration is first-class, why staged with lead-lag rotation, why coordinator over master controller, why severity-tiered actions, what this design will NOT do.
Added (corrected hardware reality for F1)#
hardware_f1.yamlupdated to match operator description:- 2 dehu units (was incorrectly described as 4 separate dehus); each is a contactor pair driving run + fan relays.
- Wet-tip contactors with built-in 2-min hardware cooldown โ software no longer enforces min_off (the contactors do).
- 2ร 9 kW IR-controlled AC presented as a single climate entity with HVAC mode-switching support.
- Exhaust system added (emergency + scheduled refresh).
- Leaf-temp sensor present โ primary supervisory variable.
Tests#
- 32 new tests in
test_control_loops.py(HVAC, dehu, humidifier, CO2, exhaust, coordinator, watchdog). - 22 new tests in
test_leaf_vpd.py(Tetens, AVP, leaf VPD, RH solver, cannabis-phase envelopes). - Total intelligence tests: 95. Full suite: 123 passing.
Added (ClimateSense โ environmental control sibling)#
- New
appdaemon/apps/crop_steering/intelligence/climate/package โ five pillars mirroring RootSense exactly (sensing, timeline, control, lights, anomaly) plus a hardware calibration module. - Hardware calibration is first-class. A YAML file
(
hardware_f1.yamlshipped) captures per-actuator quirks like "set heat pump to 27 ยฐC, room reads 29 ยฐC". Control loops commandtarget + offsetso the room actually hits target. Tests confirm the math is right and the file shape is forward-compatible with new keys. - Recipe-driven setpoints. A YAML at
/config/recipes/<name>.yamldescribes per-phase targets across the entire grow (temp / RH / COโ / VPD / PPFD / photoperiod). The shippedathena_f1_default.yamlis a 7-phase ~84-day Athena cannabis recipe. The timeline pillar resolves "what are my targets right now" from day-in-grow + day/night state and publishes per-metricsensor.climate_target_*entities. - Closed-loop control: bang-bang temp with calibration offset and
deadband, RH with hysteresis matching the existing
40_environment.yamldefaults (drop-in replacement once enabled), pulse-injection COโ with hard cap and lights-off safety, optional lights manager with PPFD ramps. - Climate anomaly scanner: temp/RH/VPD excursions, COโ low during
photoperiod, COโ overshoot, DLI undershoot prediction, sensor
unavailability. Reuses the existing
crop_steering_anomalyevent format withcodeprefixedclimate_*. - 6 new switches in the integration: 5 module-enable switches mirroring
RootSense's pattern, plus
..._climate_drives_intent_enabledto let the recipe optionally drive the cultivator-intent slider. - New number entity
number.crop_steering_climate_grow_day_offsetโ operator hand-sets day-in-grow. - 12 new unit tests covering hardware calibration math (cool/heat offsets, deadband, clipping, real F1 file load, forward-compat key filtering) and recipe loader (phase-for-day resolution, ramp defaults, validation rejection of empty phases, intent extraction).
- Total intelligence test count: 51 (was 39); full suite 79.
Added (Documentation)#
docs/SYSTEM_OVERVIEW.mdโ single source of truth for how the whole stack fits together. Hardware โ entities โ control โ dashboards. Includes the heat-pump calibration explanation and a full reference doc index.appdaemon/apps/crop_steering/intelligence/climate/hardware_f1.yamlโ F1-specific calibration file with the heat pump'scool_offset_c: -2.0documented inline.
Added (Dashboards)#
- New
Recipeview indashboards/legacyag/40_setpoints.yamlโ active phase + day-in-grow + DLI today/predicted + the 5 ClimateSense module switches + measured-vs-recipe-target overlay. - Recorder package extended with all
sensor.climate_target_*globs and the new ClimateSense switches/numbers.
Added (Phase 2 โ Adaptive Irrigation goes live)#
number.crop_steering_steering_intentโ single -100..+100 dial that drives every derived parameter via the IntentResolver.select.crop_steering_steering_mode_derivedโ Generative / Mixed-generative / Balanced / Mixed-vegetative / Vegetative bucketing of the intent slider.crop_steering.custom_shotHA service. Pure event-router with full schema validation. Orchestrator listens for the resulting event and applies safety gates.IntentResolver._publish_derived_mode()keeps the derived select in sync with the intent slider on every tick.- 9 unit tests for IntentResolver (lerp endpoints, profile-dict consistency
guard, every profile key has an entity mapping, intent=0/ยฑ100 produce
correct values, live read of dryback sliders, 11-point bucketing matrix,
derived sensor attributes). 13 tests total in
tests/intelligence/, all green.
Added (Test coverage + migration doc)#
- 26 additional unit tests across
tests/intelligence/:test_anomaly_scanner.py(8) โ emitter blockage, EC drift, flat-line gating by photoperiod, peer-deviation, no-double-fire.test_orchestrator.py(7) โ emergency rescue, flush trigger and cooldown, anomaly suppression, custom-shot event routing.test_agronomic.py(11) โ VPD math against published Athena chart values, Penman-Monteith monotonicity, transpiration envelope sanity, VPD ceiling publishing.
- Total intelligence test count: 39 passing (was 13).
- Two real bugs caught and fixed in the process: missing
_dryback_windowinit inAgronomicIntelligencereproduced via fixture, and an over-loose peer-deviation test data set that didn't actually exercise the 2ฯ rule. tests/intelligence/_appdaemon_stub.pyโ shared AppDaemon stub module so individual test files don't duplicate ~50 lines each.MIGRATION.mdโ operator-facing v2.3.x โ v3.0 upgrade guide with step-by-step rollout sequence, rollback path, and troubleshooting.
Added (Linked F1 dashboard suite)#
- Five linked Lovelace dashboards under
dashboards/legacyag/, built on the existingcustom:agency-sensor-analytics-cardthat the live F1 install already uses. Replaces the earlier history-graph-card prototype (rootsense_history.yaml) which has been removed.00_overview.yamlโ landing page, room climate + substrate- RootSense status at a glance.
10_climate.yamlโ three views (Temp & RH / COโ / VPD) with measured-vs-target overlays and full actuator control panels.20_substrate.yamlโ two views (per-table VWC & EC / RootSense per-zone intelligence). 7-day FC + dryback velocity + porosity- EC stack.
30_intelligence.yamlโ three views (Intent / Anomalies / Custom shot console). Includes one-click test-emitter and rescue-shot buttons.40_setpoints.yamlโ single page collecting every operator target, plus measured-vs-target time series for climate and substrate. Anchor for the future ClimateSense recipe timeline.
- A shared markdown nav block at the top of every dashboard links the five together for one-click switching.
Added (Plans)#
docs/upgrade/INFLUXDB_GRAPHS_PLAN.mdโ how to swap the agency-sensor-analytics-card's data fetch from HA's recorder history API to InfluxDB v2 Flux queries. ~1.5 focused-days work. Default stays HA-history; per-card opt-in viadata_source: influxdb.
Added (Polish)#
ENTITIES.mdupdated end-to-end with new RootSense entities and the dryback semantic clarification ("% drop from peak", not "VWC value to dry to").README.mdgains a RootSense v3 section with module status table and opt-in steps..gitignoreexcludesappdaemon/apps/crop_steering/state/(local SQLite) and a stray nested clone directory.
Added (Phase 1 โ Root Zone Intelligence wiring)#
- New shared base
appdaemon/apps/crop_steering/intelligence/base.pyprovides module-enable gating and common helpers for all five pillars. - 5 module-enable switches in the integration so each pillar is independently
toggleable from the HA UI:
switch.crop_steering_intelligence_root_zone_enabledswitch.crop_steering_intelligence_adaptive_enabledswitch.crop_steering_intelligence_agronomic_enabledswitch.crop_steering_intelligence_orchestrator_enabledswitch.crop_steering_intelligence_anomaly_enabledDefault OFF โ existing v2.x installs are unaffected on first upgrade.
RootZoneIntelligencenow publishes three live derived sensors per zone (previously stubs):sensor.crop_steering_zone_{n}_dryback_velocity_pct_per_hrsensor.crop_steering_zone_{n}_substrate_porosity_estimate_ml_per_pctsensor.crop_steering_zone_{n}_ec_stack_index
- Local dryback-episode tracker (
DrybackTracker) detects peak/valley pairs from the rolling per-zone VWC buffer, persists each episode torootsense.db, publishesdryback.completeon the bus, and fires the HAcrop_steering_dryback_completeevent. - Recorder includes package:
packages/rootsense/00_recorder.yamlensures the new sensors and module switches are kept in HA's history database. - Reconciliation document
docs/upgrade/RECONCILIATION.mdmapping the gap analysis P1/P2 items to RootSense plan phases. - Unit tests:
tests/intelligence/test_dryback_tracker.pycovers the episode tracker state machine (4 cases, all green).
Added (Phase 0 โ scaffolding only, no behaviour change)#
- New
appdaemon/apps/crop_steering/intelligence/package containing the four RootSense intelligence pillars as opt-in AppDaemon apps:root_zone.pyโ automated field-capacity detection, dryback episodes, substrate analytics sensors.adaptive_irrigation.pyโ cultivator-intent slider, profile interpolation, bandit-based shot-size optimisation.agronomic.pyโ Penman-Monteith transpiration estimate, VPD ceiling per cultivar, nightly run reports.orchestration.pyโ coordinator withcrop_steering.custom_shotservice, emergency rescue, EC flush guardrails.anomaly.pyโ cross-cutting anomaly scanner (emitter blockage, EC drift, sensor flat-line, peer-group VWC deviation).
- Shared infrastructure:
bus.pyโ in-process pub/sub (RootSenseBus).store.pyโ local SQLite analytics store (rootsense.db).
docs/upgrade/ROOTSENSE_v3_PLAN.mdโ full upgrade plan, feature mapping, testing strategy, and future roadmap.docs/upgrade/apps.example.yamlโ example AppDaemon app declarations.
Fixed#
- Testability hardening: moved
ShotCalculatorinto new pure helper modulecustom_components/crop_steering/calculations.pyso unit tests no longer require a Home Assistant runtime during import.
Documentation#
- Added
docs/upgrade/GAP_ANALYSIS_2026-05.mdwith a full module-by-module gap analysis, prioritized production backlog, and explicit To-Do sequence. - Updated README with a dedicated "Upgrade Gap Analysis & To-Do" section linking to the new tracker document.
Changed#
- P0 dryback is now unambiguously "% drop from peak VWC", surfaced as two
independent operator-facing number entities:
number.crop_steering_veg_p0_dryback_drop_pct(range 2โ40, default 12).number.crop_steering_gen_p0_dryback_drop_pct(range 2โ50, default 22). Defaults follow Athena cannabis guidance (small drop in vegetative growth, larger drop in generative). Both values are read live by the IntentResolver every tick โ neither is hard-coded. The interpolated current target is pushed to the existingnumber.crop_steering_p0_dryback_drop_percententity that the legacy P0-exit predicate already consumes, and exposed assensor.crop_steering_p0_dryback_drop_pct_currentfor dashboards.
- Legacy entities
number.crop_steering_veg_dryback_targetandnumber.crop_steering_gen_dryback_targetare retained as aliases. Their default values are corrected from50/40(which were too aggressive under the "drop %" semantic) to the new12/22. Their min ranges are widened to2so existing installs that already configured them lower continue to load without validation errors. DEFAULT_VEG_P0_DRYBACK_DROP_PCT/DEFAULT_GEN_P0_DRYBACK_DROP_PCTadded tocustom_components/crop_steering/const.pyas the single source of truth.
Notes#
- Existing apps (
master_crop_steering_app.py,phase_state_machine.py, detectors, profiles) are untouched in this commit. - Modules are not added to
apps.yamlautomatically; existing installs are unaffected. Seedocs/upgrade/apps.example.yamlto opt in. - Full migration to v3.0 happens incrementally across PRs #2โ#5 per the plan.
[2.3.1] - 2025-01-03
๐ง Critical Fixes & Documentation Overhaul#
- CRITICAL FIX - Reconstructed corrupted
config_flow.pythat prevented integration loading - Documentation Accuracy - Complete rewrite of all .md files to reflect actual system capabilities
- Code Quality - Major refactoring with 85% reduction in code duplication
- Dependency Clarity - Fixed misleading "zero dependencies" claims (AppDaemon required for automation)
๐ Documentation Updates#
- README.md - Updated to v2.3.1, corrected dependency information
- installation_guide.md - Complete beginner-friendly rewrite with step-by-step instructions
- dashboard_guide.md - Rewritten for actual AppDaemon YAML dashboard system
- troubleshooting.md - Updated for rule-based system (removed AI/ML references)
- Removed -
ai_operation_guide.md(contained incorrect AI/ML information)
๐ก System Clarification#
- Rule-Based Logic - System uses sophisticated rule-based irrigation logic, not AI/ML
- Statistical Analysis - Uses scipy.stats for trend analysis and sensor validation
- AppDaemon Requirement - AppDaemon needed for automatic phase transitions and advanced features
- Core Integration - Works standalone for manual control, AppDaemon adds automation
๐๏ธ Code Quality Improvements#
- Helper Classes - Introduced
ShotCalculatorto eliminate code duplication - Constants - Replaced magic numbers with named constants in
const.py - Version Consistency - Standardized version strings across all files
- Import Optimization - Fixed missing imports and standardized patterns
โ ๏ธ Truth in Documentation#
- No More AI Claims - Removed all references to machine learning and AI capabilities
- Accurate Feature List - Documentation now reflects actual implemented features
- Realistic Expectations - Clear distinction between manual and automated operation modes
- Beginner Focus - All guides rewritten for new users with no assumptions
[2.3.0] - 2024-12-26
๐ Major Features#
- Full GUI Configuration - Complete zone and sensor setup through Home Assistant UI
- Zero Dependencies - Removed ALL external Python packages (numpy, pandas, scipy, plotly)
- Clean Architecture - Removed all redundant scripts and command line tools
- Fixed Phase Logic - P3 now correctly persists through entire lights-off period
- System-wide Light Controls - Removed illogical per-zone light controls
๐๏ธ Removed (Cleanup)#
fix_appdaemon_requirements.sh- No longer needed, zero dependenciesrequirements.txt- System uses only standard Python librariesconfigure_crop_steering.py- Replaced by GUI configurationadvanced_crop_steering_dashboard.py- Uses YAML dashboards instead- Command line dependency for zone configuration
โจ Improvements#
- GUI Config Flow - Advanced setup with sensor configuration for each zone
- YAML Dashboards - AppDaemon native dashboards without Plotly
- Async/Await Fixes - Proper coroutine handling in AppDaemon
- Sensor Fusion - Fixed VWC/EC mixing issue
- State Machine - Clean phase management implementation
๐ Documentation#
- Updated README to v2.3.0 with GUI configuration
- Removed all references to deprecated scripts
- Added proper crop steering terminology explanations
- Marked zone_configuration_helper.py as deprecated
โ ๏ธ Breaking Changes#
- None - existing configurations continue to work
[2.1.1] - 2024-12-21
๐ AppDaemon v15+ Compatibility Update#
- Updated all documentation for AppDaemon v15+ directory changes
- Fixed file paths from
/config/appdaemon/to/addon_configs/a0d7b954_appdaemon/ - Enhanced fix script to auto-detect AppDaemon version and paths
- Added migration guide for upgrading from AppDaemon v14 to v15+
- Updated installation instructions with correct Samba share paths
๐ Documentation Updates#
docs/installation_guide.md- Updated for AppDaemon v15+ pathsdocs/appdaemon_v15_migration.md- NEW comprehensive migration guidedocs/dynamic_zones_guide.md- Added AppDaemon v15+ compatibility notesfix_appdaemon_requirements.sh- Enhanced to handle both old and new pathsREADME.md- Updated setup instructions for AppDaemon v15+
๐ง Technical Changes#
- Auto-detection of AppDaemon directory structure in scripts
- Backward compatibility maintained for AppDaemon v14 and earlier
- Improved error messages for missing AppDaemon directories
- Updated Samba share paths in all documentation
โ ๏ธ Important Notes#
- If using AppDaemon v15+: Files are now in
/addon_configs/a0d7b954_appdaemon/ - Samba access: Use
\\YOUR_HA_IP\addon_configs\a0d7b954_appdaemon - Migration required: Run updated scripts to move files to correct locations
[2.1.0] - 2024-12-21
๐ Major Features Added#
- Dynamic Zone Configuration - Support for 1-6 irrigation zones (previously hardcoded to 3)
- Interactive Zone Setup Tool -
zone_configuration_helper.pyfor easy zone configuration - AppDaemon Compatibility Fix -
fix_appdaemon_requirements.shresolves scikit-learn installation issues - Flexible Entity Creation - Automatic generation of sensors and switches per configured zone
โจ New Components#
zone_config.py- Zone configuration parser and validatorzone_configuration_helper.py- Interactive setup wizardfix_appdaemon_requirements.sh- AppDaemon compatibility fix scriptdocs/dynamic_zones_guide.md- Complete zone configuration documentationservices.yaml- Proper service documentation
๐ง Enhancements#
- Config Flow Improvements - Two setup modes: automatic (from env file) or manual (UI wizard)
- Service Updates - Dynamic zone validation in irrigation services
- Entity Scaling - All components now create entities per configured zone
- Error Handling - Improved validation and graceful error handling
- Documentation - Updated all guides for v2.1.0 features
๐ Bug Fixes#
- Fixed hardcoded zone iteration in AppDaemon modules
- Resolved entity creation for zones 2+ (previously only zone 1 worked)
- Fixed service validation to accept configured zones instead of hardcoded 1-3
- Removed obsolete package references in config flow
- Fixed sensor averaging to use actual configured sensors
๐๏ธ Architecture Changes#
- Zone Detection - Dynamic zone discovery from configuration
- Entity Generation - Runtime entity creation based on zone count
- AppDaemon Integration - Removed hardcoded zone loops
- Configuration Management - Centralized zone config through new parser
๐ Per-Zone Entities Created#
For each configured zone:
switch.crop_steering_zone_X_enabled- Zone enable/disableswitch.crop_steering_zone_X_manual_override- Manual controlsensor.crop_steering_vwc_zone_X- Average VWC for zonesensor.crop_steering_ec_zone_X- Average EC for zonesensor.crop_steering_zone_X_status- Zone operational statussensor.crop_steering_zone_X_last_irrigation- Last irrigation timestamp
๐ Migration Notes#
- Existing 3-zone setups continue working without changes
- New installations can configure any number of zones 1-6
- Zone configuration now centralized in
crop_steering.env - Helper scripts automate setup process
โ ๏ธ Breaking Changes#
- None - fully backward compatible with existing installations
๐ฎ Future Enhancements#
- Zone grouping for simultaneous irrigation
- Zone-specific crop profiles
- Individual zone scheduling
- Water usage tracking per zone
[2.0.0] - 2024-12-15
๐ Major Release - Complete System Overhaul#
- New Architecture - Integration + AppDaemon AI modules (replaced package-based approach)
- Advanced AI Features - Machine learning, sensor fusion, dryback detection
- Professional Dashboard - Real-time Plotly visualizations
- Quality Assurance - Complete code validation and optimization
- Production Ready - Comprehensive testing and error handling
โจ AI Features#
- Machine Learning Engine - Predictive irrigation with ensemble models
- Intelligent Sensor Fusion - Multi-sensor validation with outlier detection
- Advanced Dryback Detection - Peak detection algorithms
- Intelligent Crop Profiles - Strain-specific optimization
๐๏ธ Architecture#
- Home Assistant Integration - Native HA integration with config flow
- AppDaemon AI Modules - Advanced machine learning and automation
- Configuration Management - File-based hardware configuration
- Real-time Dashboard - Professional monitoring interface
๐ Documentation#
- Complete installation guide for beginners
- AI operation guide
- Dashboard usage guide
- Troubleshooting documentation
[1.x] - Legacy Versions
Previous package-based implementations. See git history for details.
Controller
The companion controller app has its own changelog file. It is mirrored to the controller repository for existing installations.
0.16.2
Pair with integration 2.19.2. C3. Found on the first real install (a one-zone tent); the fixes themselves were not run on hardware before release.
- Zones are never invented. Started before the integration was set up, the controller fell back to the shipped
num_zones: 3and reported zones 2 and 3 of a one-zone tent as "no hardware mapped". It now has no zones until a room exists, checks every loop, and picks the room up by itself: no restart needed. The log says so: "the Crop Steering integration has not published a room yet...".num_zonesis still the fallback when Home Assistant cannot be reached at start, and a hand-mappedhardwareoption still keeps its zone count. A state file that already holds the phantom zones loads as before. - Switch-on is no longer an irrigation event. New switch-on timestamps are explicitly marked by
last_shot_is_anchorand published asunknownuntil an irrigation is recorded, also for a room that is off. An old file without the flag keeps its timestamp: zero daily counters or missing history cannot establish whether an old timestamp was switch-on or irrigation. Electrical operation alone does not prove water delivery. - Final review fixes keep the descriptor's complete zone list when HA sensors appear gradually, preserve old irrigation timestamps after daily rollover, and keep legacy numeric-string/malformed excluded-volume state loadable. Regression tests cover each case.
- The dashboard served by the app is the 2.19.2 build (the side menu scrolls on small screens).
- No change to add-on options. No change to what a working install waters, or when.
0.16.1
Pair with integration 2.19.1. Not run on hardware before release.
- The controller reports its own version:
controller_versionin every room'sai_heartbeat, and in the first log line (f2-control 0.16.x starting | rooms ...). The dashboard's sidebar shows it next to the integration's. It is read from theconfig.yamlthe image was built from (copied in as/app/addon.yaml), so there is no second number to keep in step. - No change to irrigation behaviour, options or the state file.
0.16.0
Pair with integration 2.19.0. C3. Released without a staging soak by decision of its two operators; not run on hardware before release.
- A room can DECLARE its plumbing (
plumbingin the engine descriptor, set in the integration's setup). Declared: the mapped pump and main-line have to match it, or the room is held with a reason, nothing opens and nothing is counted. This closes the 2.18.0 case where a pumped room with no pump mapped was watered with the valve open and no pump, and the shot counted as delivered. - Never declared (every existing install): no change, and no disarm cycle after the update. The layout joins the saved setup fingerprint only when it is present, so the fingerprint 0.15.x saved still matches.
- A layout this controller does not know (a newer integration) is held, not guessed.
- No change to add-on options or to the state file.
0.15.2
Pair with integration 2.18.1. No change to irrigation behaviour, options or saved state.
- Test seam: the state file location can be overridden with the
F2_STATE_PATHenvironment variable. Unset, as on every install, it is/data/state.jsonexactly as before. The constructor reads that file, and on adopting a setup writes it, before a test can redirect it; GitHub runners have no/data, so CI never noticed, but on any machine where/dataexists and is writable (a devcontainer, this add-on's own container) the test suite wrote a real file there and leaked it into the next test. - Bundles nothing new; the dashboard is the 2.18.0 build.
0.15.1
- A room switched off stays off while Home Assistant restarts. The room's on/off switch reads unavailable for a moment during a core restart, and unavailable used to mean on: an empty room began a fresh run and raised its probe alerts again. The controller now keeps the last value it read (also across its own restart) and only treats a switch it has never seen as on, which is what keeps integrations older than the switch watering.
0.15.0
Pair with integration 2.18.0. Existing options, engine flags, room IDs, counters and learned state are kept. Updating from 0.14.0 resumes by itself; no kill-switch cycle is needed.
- A zone needs only its valve. Pump and main-line valve are optional, so a room with one smart plug or solenoid per zone irrigates. Lead times are skipped with the hardware they belong to; the close read-back and the hardware-fault latch cover whatever the room has. Three-switch rooms run exactly as before.
- The source-water EC probe may report ยตS/cm; it is converted to mS/cm before the sanity range and the gate. Previously such a probe read as out of range and the gate blocked every shot without saying why.
- The Cloudflare judge manages P2. With Auto Setpoints on and
cf_account_id+cf_api_tokenset,typesafe/jevis asked once per clock hour during P2 (lights on, no dated plan). It may move the zone'sp2_shot_sizewithin 1-4 % and hold the working peak within 2 points of the learned one: one bounded step per lever per grow-day. A tripped guard, a low-confidence answer, or no answer in 5 s changes nothing; irrigation never waits on it. Status attributesjev_last,jev_changed_today,working_peak_adjust. - Bundles the 2.18.0 dashboard (setup helpers in Rooms & setup).
0.14.0
Pair with integration 2.17.0. Existing options, engine flags, room IDs, counters and learned state are kept. After this update, turn the engine kill switch off and on once: the previous build saved no record of the setup it had accepted, so this first start has nothing to resume from. Later restarts and reboots carry on by themselves.
- Room on/off:
switch.crop_steering_<prefix>room_activeoff means no irrigation of any kind and no alerts for that room. - P1 no longer ends on a clock. It runs until the target is recovered after
p1_minimum_shots, orp1_maximum_shotsis reached. - Auto Setpoints (off by default): learns each zone's ceiling, gain and dry-down; on a P1 plateau hands over to P2 and carries the achieved peak forward. Optional Cloudflare
typesafe/jevcheck via the newcf_*options. - The accepted setup revision is saved with a fingerprint, so an unchanged setup resumes after a restart with the kill switch left on (hardware must read off). A pending setup raises a notification naming what must read off.
- Switch read-back after a shot re-reads for up to 6 s instead of once at 1 s: a late Zigbee report no longer latches a false hardware hold.
- Bundles the 2.17.0 dashboard (recorded sensor data and the projected P0-P3 day on the Today graph).
0.13.3
- Combine daily targets and dated plans under Irrigation plan, with Today and Schedule views.
- Join schematic VWC and EC references through the night and into the next day.
- Show effective scheduled targets instead of disabled manual fallback fields when a schedule owns the room.
- Dashboard-only correction; controller decisions and stored settings are unchanged.
0.13.2
- Bundle graphical tank/pump telemetry and current zone state with last irrigation timestamps.
- Publish irrigation event timestamps with an explicit timezone offset.
- Include synthetic demo recipes/runs and the Home Assistant sidebar recovery button.
Changelog โ Crop Steering add-on
The full project changelog (integration + add-on) lives at https://github.com/JakeTheRabbit/HA-Irrigation-Strategy/blob/main/CHANGELOG.md.
0.13.1#
- Read the same-room legacy maximum-shot-duration entity when the canonical entity is absent, matching the dashboard calculator and reviewed edits. Reject an invalid existing duration cap before actuation.
- Bundle the user-authored recipe library and pair with integration 2.15.0. Existing room identities, options and persistent controller data remain compatible.
0.13.0#
- Pair with integration 2.14.0 for reactive manual setpoint previews, run comparisons and clear zone/per-plant water calculations.
- Calculate new delivery counters from configured zone flow and elapsed valve runtime, including capped/truncated/minimum shots and partial aborts. Snapshot sizing before each shot so a mid-shot configuration edit cannot rewrite its volume.
- Keep existing options, engine flags, room IDs, learned state and old daily/weekly totals. Delivery remains a flow-based estimate, not a meter reading.
0.12.1#
- Correct room names in the bundled workspace when HA descriptor sensors share a generic friendly name.
- Pair with integration 2.13.2. Irrigation decisions, options and persistent state format are unchanged from 0.12.0.
0.12.0#
Update the Crop Steering integration to 2.13.0 before updating this controller. Existing options, enable flags, room IDs and persistent runtime data are retained.
-
Serve the native dashboard with per-zone day/week grow plans and a combined VWC/EC planning graph.
-
Read versioned setup and strategy snapshots, with explicit configuration acknowledgement and safe holds for invalid required plans.
-
Correct low-flow sizing, elapsed duration accounting, volume caps, stale sequential decisions and shared-hardware recovery.
-
Remove historical package examples from the installation bundle; sensor and equipment mapping belongs to the integration.
-
Missing pore EC keeps base VWC shot sizing and pauses EC offset/PID adaptation, with a visible degraded status.
-
Feed EC/pH and pore readings require finite values and valid, fresh, timezone-aware timestamps (60 seconds future-skew tolerance).
-
Weekly delivery estimates persist seven grow-day buckets, include interrupted delivery and expose incomplete legacy history.
0.11.0#
โ ๏ธ Update the Crop Steering integration BEFORE rebuilding this add-on. The engine now reads the default room's pump/mainline/valves from the integration's
sensor.crop_steering_engine_config. On an old integration that doesn't publish it, the default room holds safe (no watering) until the integration is updated (the engine re-checks every 5 minutes and resumes on its own).
- Portability: default-room hardware comes from the integration's engine_config descriptor โ the hardcoded F2 pump/valve fallback is gone. An unmapped room holds safe with a clear "no hardware mapped" reason instead of actuating another install's entities.
- Kill switch works mid-shot: shots are delivered in โค2 s slices that re-check the kill switch and the zone's manual override; an abort closes the valve immediately and counts only the water actually delivered.
notify_servicedefault is now empty โ unset means persistent notifications only (no more default pointing at the developer's phone). If you had it set explicitly, nothing changes.- Dosing/fill/flush holds are now the
hold_entitiesoption (empty default). The previously hardcoded F2 hold entities (input_boolean.nutrient_dosing_active,input_boolean.f2_fill_mode,input_boolean.f2_flush_mode,switch.tank_filling) are no longer checked automatically โ add yours tohold_entitiesin the Configuration tab or that gate stays off. - Live room discovery: rooms added in the integration UI join within
rediscover_seconds(default 300 s) without an add-on restart, fail-safe OFF. - Missing-setpoint alerts: if a
number.crop_steering_*setpoint entity disappears (per-zone AND global) for 3+ loops, you get one rate-limited alert + a vitals line instead of the engine silently running built-in defaults. - Timezone hardening: tzdata baked into the image; startup logs the effective local time and alerts if the container clock disagrees with Home Assistant's configured zone.
- Heartbeat sensors publish the room's
enable_flag(used by the integration's Repairs health checks); vitals title uses the newinstance_nameoption.
0.10.5#
- Fix: zones could freeze in P2 if the fused-sensor entity_id didn't match. The engine read
each zone's probe at
sensor.crop_steering_<room>vwc_zone_N/ec_zone_N, but on a box first set up under an older integration the HA registry keeps the legacy id..._zone_N_vwc/_zone_N_ecforever. The mismatch made every zone read as "blind" โdecide()was skipped โ the P0-P3 phase machine stopped (it never even forced P3 at lights-off), and the zones sat on a blind timer. The engine now resolves each fused sensor under both naming conventions (and_detect_zonescounts either), so it finds the probe regardless of which id the registry assigned. - Hardening โ a dead probe can no longer strand the daily cycle. A blind zone still honours the time-based phase forces (lights-off โ P3, P3 โ P0 at the new photoperiod) even while VWC-driven steering is paused, so it can't freeze overnight. The "probe dead" alert now repeats (every ~30 min while blind) with the exact entity_id the engine is looking for, instead of firing once and going silent.
0.10.4#
- Engine Log panel now works. It used to tail
/local/f2_engine.log, which the add-on can't write (no/configmap) โ so it was always empty. It now reads the engine's published decision feed (sensor.crop_steering_activity_log), room-scoped, and only re-renders when the feed changes (also removes the 4s render churn). - "Next" on the irrigation-frequency card. The engine now publishes a per-zone next-shot estimate
sensor.crop_steering_<room>_prediction_zone_N_next_irrigation_hours(P2: time for VWC to dry to the re-water threshold at the current dryback rate; "โ" when not computable). - Safety (HA-down observability): the shot CLOSE sequence now checks every
turn_offand reads the valve back HA-aware โ a failed/unconfirmed close (e.g. HA unreachable mid-shot) raises a CRITICAL alert instead of silently reading as "closed". The shot is still counted (water was delivered) so the daily cap stays honest. NOTE: software cannot close a valve when HA is down โ the hardware fail-safe (NC valves, pump-relay default-off, an independent watchdog) is the real guarantee.
0.10.3#
- Diagnostic: f2.html logs one
[f2-perf]console line per 30s tick (JS heap, DOM-node count + delta, Chart-instance count) to pinpoint the dashboard lag. Open the console, filter[f2-perf], leave it until it lags, and watch which number climbs. Harmless; removed once the leak is found.
0.10.2#
- Fix: per-zone "Volume fed vs daily cap" + "Irrigation frequency" tiles were blank (โ). The engine
now publishes
sensor.crop_steering_<room>_zone_N_daily_water_app(litres fed today) and..._irrigation_count_app(shots today) โ the data was already tracked in zone state, just not republished. Resets at the lights-on (P3โP0) rollover like the other daily counters. (Per room.)
0.10.1#
- Per-room dashboards (
?room=<slug>). The operator console (f2.html) and the mobile one-pager (overview.html) now scope to an additional room with?room=f1etc. โ everycrop_steering_*read/write is routed to that room's entities (two chokepoints, no per-id edits), the kill-switch button controls the room's kill switch, and a badge shows which room you're viewing. No?room=(default room) is unchanged.
0.10.0#
- Multi-room engine. One add-on now drives every configured room, not just the first.
Refactored around a
Roomabstraction; each room is a fully self-contained control loop with its own pump/mainline/valves, reservoir pH/EC feed gate, photoperiod, kill switch and durable state, namespacedcrop_steering_<slug>_*. Additional rooms are discovered from the integration's publishedsensor.crop_steering_<prefix>engine_configdescriptors and come up fail-safe OFF (switch.crop_steering_<slug>_engine_enabled, default off). The default room is byte-identical to before./data/state.jsonis nested by room; an old flat single-room file still loads transparently.
0.9.1#
- New logo (cannabis leaf + green growth chart + rising arrow, with the Home Assistant and Python marks). Updated the add-on icon and logo.
0.9.0#
- Vmax advisory. The engine now watches each zone's morning P1 wet-up and publishes the detected
field-capacity ceiling as
sensor.crop_steering_zone_N_vmax_detected(with a confidence attribute). Advisory only โ it does not change any irrigation decision. Pairs with the integration's named-stage recipes (2.10.0). - Re-synced the vendored
crop_steering_enginewith the canonical source.
0.8.3#
- Use the original Open Crop Steering logo artwork (leaf + water-drop in a green ring) for the add-on icon and logo, instead of the redrawn version.
0.8.2#
- New logo (cannabis leaf + growth chart), matching the project mark.
0.8.1#
- Fix: the dripper flow rate + drippers/plant now drive shot length even if a per-zone plant count is unset (it used to fall back to a generic flow value). Plant count cancels out of the duration maths, so it no longer gates the dripper settings.
0.8.0#
- Generic out of the box: the source-water pH/EC feed gate is now optional. Set
feed_ec_sensor/feed_ph_sensorto your reservoir probes to enable it; leave them blank to disable it (dosing / tank-fill holds still apply). Removed the hardcoded F2 probe defaults; the substrate/flow fallbacks are neutral placeholders.
0.7.0#
- Renamed F2 Control โ Crop Steering with a new logo. Proper description + feature list (Documentation tab) and this changelog. The dashboards are served as a sidebar panel (toggle Show in sidebar on the Info tab).
0.6.0#
- Configure once: lights hours and zone count are now read from the integration, so the add-on options for them are just fallbacks. Ends the "lights out of sync" class of bug.
0.5.0#
- The engine reads each zone's fused sensor (
sensor.crop_steering_vwc_zone_N/ec_zone_N), so you can add any number of probes per zone in the integration UI and the engine uses all of them (averaged, outliers rejected). Removed the hardcoded zone sensors.
0.4.0#
- Bundled the operator dashboards into the add-on, served over Home Assistant ingress
(sidebar panel) with a Live/Demo chooser โ no more copying
f2.htmlto/config/www.
0.3.0#
- Friendlier title + custom icon; help tooltips on every Configuration option.
0.2.0#
- First standalone add-on release: pure crop-steering engine + REST IO + 30-min vitals, gated by the kill switch. Replaces the retired legacy engine.
Source files: CHANGELOG.md and addons/f2_control/CHANGELOG.md.