Skip to content

Updating from 3.17 or earlier? Read the upgrade guide first.

Crop Steering
Browse the documents

Calibration

Applies to release 3.18.1 · View source at the tag · Markdown

On this page

Bottom line: three kinds of calibration change the numbers the system works from:

  • a moisture probe offset (and optionally a curve), so a probe reads what your handheld meter reads;
  • a 2-point feed pH/EC calibration, so the feed-water probes read true in buffer solutions;
  • a dosing pump calibration, so a pump without a flow sensor doses the right volume.

Each one is checked before it is applied, stored and written to the journal. A probe offset is also read back from storage after it is saved. Two of them hold things on purpose while they run, and that is safety-critical:

  • While a feed probe calibration session is open, the probe sits in a buffer, so its reading is not used as the feed. Batches and pH auto-correct wait. In a room with a feed EC or pH band set, the source-water gate holds that room's shots until the session ends.
  • If a dosing pump does not read off after a prime, calibration run or stop, the controller latches the room (CS-801). Its shots are held and the off is sent again every 2 s until the pump reads off. A latch from a prime or calibration run survives a controller restart; one from a stand-alone stop is held in memory only.

Where to do it today:

CalibrationDashboardServices
Moisture probe offset (match handheld)Equipment › Probes & offsetscrop_steering.probe_offset_set
Moisture probe curveShown read-only on Equipment › Probes & offsetscrop_steering.probe_offset_set (curve)
Feed pH/EC 2-point wizardEquipment › Feed pH/EC calibrationcrop_steering.calibration_start … calibration_commit
Feed probe check (judged only, never applied)Equipment › Probes & offsets › Feed probe calibrationcrop_steering.calibration_record
Dosing pump prime and calibrateEquipment › Prime & calibrate a pumpcrop_steering.pump_precheck, pump_prime, pump_stop, pump_calibrate, pump_calibration_commit

An AI assistant or an automation can call the same services from Developer Tools › Actions in Home Assistant, with Return response ticked.

Who can do what

ServiceWho may call it
calibration_get, calibration_statusAny signed-in user
calibration_record, calibration_start, calibration_capture, calibration_commit, calibration_revert, calibration_abandon, probe_offset_setAdministrator
pump_precheckAnyone (it only reads)
pump_stopAny signed-in user, or an automation
pump_prime, pump_calibrate, pump_calibration_commitAdministrator

The MCP server reaches probe_offset_set only through a reviewed proposal (preview_probe_offset, then apply_proposal, with writes enabled). It cannot start, capture or commit a feed calibration, and it cannot prime, calibrate or stop a pump.


Moisture probe offsets and curves

What an offset does

An offset is a number of percentage points added to one moisture probe's reading. Say your handheld meter reads 52 % at the same spot where the probe reads 49 %: the offset is +3.

The offset is applied before the zone's probes are combined (fused) into the zone's VWC. So it changes the VWC the engine steers on, the zone's dryback and every shot decision based on them. Watch the zone for a while after you change one.

Limits:

  • An offset is between -20 and +20 points. A handheld match that needs more than 20 points is refused: the probe reads too far from the handheld, so check the probe and where it sits instead.
  • The corrected reading must stay inside 0–100 %.
  • Setting the offset to 0 with no curve removes the probe's entry.

A probe with an offset or a curve also gets its own sensor, sensor.<probe object id>_corrected (for example sensor.f2_z1_vwc_a_corrected). It shows the corrected value in %, with attributes source, raw, offset, curve and set_at. It goes unavailable while the raw probe is unavailable, so an old value is never shown in its place.

Match a handheld meter (dashboard)

  1. Open Equipment › Probes & offsets and pick the room.
  2. Press Match handheld on the probe's row.
  3. Take a handheld reading at the same spot and depth as the probe, and type it in (0 to 100).
  4. The page asks the integration for a preview. It shows the offset it would set and what the zone would read with it. Nothing is saved yet.
  5. Press Apply +N offset to in the Review & apply bar. The write names the store's revision. If someone changed the offsets meanwhile, it is refused and you reload. After the save, the page reads the offset back with calibration_get.

Curves

A curve turns a raw probe reading that is not moisture into moisture in %. Only a probe that reports dielectric permittivity can take one. Its unit must name ε, epsilon, permittivity or dielectric. A probe that already reports % is refused with "This probe already converts to moisture. A curve only applies to raw readings." Any other unit is refused as well.

Two curves exist:

CurveFitted overWorst error of the fit
generic_soillesspermittivity 3–30 (coir and peat blends)0.42 points
generic_rockwoolpermittivity 10–60 (wet slabs)2.7 points

Both are quadratic fits of the Topp et al. (1980) equation for mineral soil. They are generic approximations, not substrate calibrations. Every answer that sets a curve carries this warning. After you pick a curve, match a handheld meter so the offset corrects the reading at the moisture you steer around.

The dashboard shows the room's curve in use and why a curve does not apply, but it does not set one. To set or clear a curve, call the service (curve: none clears it).

crop_steering.probe_offset_set

FieldRequiredMeaning
room_idyesCanonical room id, for example room:abc123
entity_idyesOne of the room's moisture probes
offsetone of these threePoints, -20 to +20
match_handheldHandheld reading 0–100. The offset becomes handheld minus the probe's bare reading. You cannot send it together with offset.
curvegeneric_soilless, generic_rockwool, or none to clear
previewnotrue: answer with the effect and save nothing
expected_revisionfor a writeThe revision a preview returned

A preview answers offset, curve, corrected_now, fused_before, fused_after (what the zone would read), excluded_after (probes the fusion would leave out, and why), diff and revision. A write answers the same without preview and diff, with the new revision. fused_before and fused_after are estimates. They mirror the zone's fusion without its learned memory of the probes.

action: crop_steering.probe_offset_set
data:
  room_id: room:abc123
  entity_id: sensor.f2_z1_vwc_a
  match_handheld: 52
  preview: true

Each write is read back from storage. If the read-back does not match, the old value is put back and the call fails with "The probe offset did not read back from storage. It was not applied." A successful write is journaled as config.changed, with store: calibration and section: probe_user_offsets, so it can be seen and put back from History › Changes.


Feed pH/EC 2-point calibration

Equipment › Feed pH/EC calibration: the guided 2-point calibration at the tank

What it does

The wizard watches the room's raw feed pH or EC probe while it stands in two reference solutions. From the two points it works out:

  • slope (%): 100 % means each step reads true;
  • offset: how far the probe is out at the anchor (pH 7, or EC 0 mS/cm);
  • a verdict: pass, drifting or fail.
VerdictSlopeOffset (pH units, or mS/cm)
pass95–105 %within ±0.1
drifting90–110 %within ±0.3
failanything else

A commit applies a pass or drifting result. A fail is recorded but never applied. The call then fails with a message telling you to clean or replace the probe and calibrate again.

The raw sensors are never changed. Applying a calibration does two things:

  • sensor.crop_steering_<prefix>feed_ph_corrected and sensor.crop_steering_<prefix>feed_ec_corrected read the corrected value. They are unknown while no calibration is applied.
  • The room's sensor.crop_steering_<prefix>engine_config carries feed_calibration ({ph|ec: {gain, bias, calibrated_at}}), the same correction as raw × gain + bias.

The controller reads the corrected sensor while it is live. Otherwise (for example right after a restart) it applies gain and bias to the raw reading itself. While a calibration is in force, the controller never uses the raw number uncorrected. It saves the calibration in its own state, so the correction holds even across a restart with Home Assistant down.

Default solutions and rules

ProbeDefault solutionsSmallest gap between them
feed_phpH 7.0 and pH 4.01.0 pH
feed_ec1.413 and 2.77 mS/cm0.5 mS/cm

You can pass your own two values in solutions. They must be inside the probe's range (pH 0–14, EC 0–20) and the minimum gap apart.

A point is captured only once the reading has held still for 30 s:

  • pH: it moved less than 0.02 over the last 30 s;
  • EC: it moved less than 1 % of its mean over the last 30 s.

A start is refused if the probe has not reported for 2 minutes ("No reading from the feed pH probe for 2 min. Check it's plugged in.").

Step by step (services)

  1. Start. calibration_start with room_id, probe: feed_ph (or feed_ec) and optionally solutions. It answers with session_id, solutions, raw_entity_id, temperature_entity_id (the room's tank temperature sensor, if one is set), dosing_paused: true and expires_at. A new start replaces any probe session already open in the room.
  2. First solution. Rinse the probe and put it in the first solution (pH 7.0 by default). Poll calibration_status with the session_id. It answers reading, raw, stable, drift, stable_s, points_captured and expires_at. Status only reads: it never captures and never extends the session.
  3. Capture point 0. calibration_capture with session_id and point: 0. If the reading is not steady yet, nothing is stored. Under API v2 the call fails with guard_refused, "The reading is not steady yet: nothing was captured." force: true captures it anyway, and the point is marked forced in the record.
  4. Second solution. Rinse, move to the second solution, wait for stable, then capture point: 1.
  5. Commit. calibration_commit with session_id. It answers record_id, slope_pct, offset, verdict, applied: true and the store revision. The session closes.

To stop without applying anything, call calibration_abandon with the session_id. A session also ends by itself 30 minutes after its last start or capture. After that, every step answers "Calibration session expired. Start again."

action: crop_steering.calibration_start
data:
  room_id: room:abc123
  probe: feed_ph

Putting one back

calibration_revert with room_id and probe takes the applied calibration off the probe and puts back the one it replaced, if there was one. If not, the probe reads raw again. It also closes any open probe session in the room. It answers reverted_record_id, record_id (the one now in force, or null) and applied.

Due reminders

A calibration committed by the wizard, or a check saved with calibration_record, is due again 30 days later. A failed one does not reset the clock. When a probe is due:

  • binary_sensor.crop_steering_<prefix>calibration_due turns on (attributes probes, due_at);
  • a Repairs card is raised, CS-612.

Records made before this release have no due date and never go due.

The check on Probes & offsets is not the wizard

Equipment › Probes & offsets › Feed probe calibration saves a check made by hand with calibration_record: you enter the two references and what the probe read in each, and it shows the verdict. Such a record only judges the probe. It is never applied and does not change any reading.

Safety: what waits while a session is open

While a feed probe session is open, the room's sensor.crop_steering_<prefix>dosing_config carries calibrating ({probe, session_id, until}), and the controller treats that probe as sitting in a buffer:

  • Batches and pH auto-correct wait ("feed probe calibration in progress").
  • A single dose by hand still goes ahead.
  • Pump primes and calibration runs are refused (pump_precheck blocker calibrating).
  • Shots: the source-water gate does not read the feed probe. If the room has a feed band set for that probe's quantity (EC or pH), the room's shots are held with reason "feed probe calibration in progress". There is no grace period and no rescue past it. Without a band, the gate is off and shots carry on.
  • Fail-closed: if the room has a feed sensor and a feed band set for that quantity, and dosing_config cannot be read while a calibration is applied to it, shots are held with "source-water pH/EC calibration state unreadable … holding (fail-closed)". Without a band, an unreadable dosing_config holds nothing.

Finish, abandon or let the session expire to release these holds. If you walk away, everything resumes within 30 minutes of the last capture.

Journal events

calibration.started, calibration.captured (each captured point, and each calibration_record check), calibration.committed (with applied and superseded_record_id) and calibration.reverted.


Dosing pump prime and calibrate

Equipment › Prime & calibrate a pump: prime the line, run a timed dose and enter what came out

What it does

A prime runs one dosing pump's power switch for 1–30 s to fill its line. A calibration run runs it for 5–120 s into a measuring jug. You then enter the mL it gave. That becomes the pump's calibrated_ml_min, which is measured mL ÷ the seconds it actually ran × 60.

What the calibrated flow is used for:

  • When the pump has no flow entity, calibrated_ml_min is the flow doses are sized from.
  • When the pump's flow entity is a number or input_number, the commit also sets it to the new flow in mL/s and reads it back (flow_entity_written). A flow entity always wins over calibrated_ml_min. A sensor flow entity is never written.

A pump keeps its newest 30 calibrations, each with the previous flow.

The pump needs its own power switch (power_entity). Without one, or while that switch is missing or unavailable, a timed run is refused.

Step by step (services)

  1. Check. pump_precheck with room_id and pump. It answers {ok, blockers}. Each blocker has a code and a message:

    CodeMessage
    no_power_entityNo power switch: a timed run needs the pump's own power switch (also raised when the switch is set up but its state is missing or unavailable)
    batch_runningRoom dosing is running
    mix_pump_onMix pump is on
    tank_fillingTank is filling
    calibratingFeed probe calibration in progress
    pump_calibratingAnother pump's calibration is still open
    controller_offlineController is offline

    controller_offline is raised when the controller's heartbeat is older than 10 minutes.

  2. Prime (optional). pump_prime with room_id, pump and seconds (1–30). The controller refuses a prime of a pump that has no usable flow yet (" is not calibrated"). On a new pump, calibrate first.

  3. Calibrate. Put the pump's outlet in a measuring jug. Call pump_calibrate with room_id, pump and seconds (5–120). It answers session_id, request_id and outcome: requested. If a blocker stands, it answers outcome: refused with the blockers (API v2: guard_refused).

  4. Wait for the run to end. The controller takes the request within 2 s, switches the power on, reads it back on, runs for the seconds asked and switches it off, then reads it back off. The time it ran (ran_s) is reported in sensor.crop_steering_<prefix>dosing under handled_results. dosing_get shows the open session with ran_s and result.

  5. Measure and commit. Read the jug. Call pump_calibration_commit with room_id, the session_id and measured_ml. It answers ml_min, previous_ml_min and flow_entity_written. A commit before the run is reported is refused ("Wait for the pump to stop"), and so is one for a run that did not run.

A report restarts the session's 30 minutes, so you have time to read the jug.

action: crop_steering.pump_calibrate
data:
  room_id: room:abc123
  pump: part_a
  seconds: 60

Stopping a run

pump_stop with room_id and pump ends that pump's prime or calibration at its next 1 s check. It switches the power off and reads it back. It never ends a batch, dose or pH correction (use the room Stop for those).

A stop for a pump while another pump's request is still waiting is turned into a room stop, which switches off every pump. A waiting dose or batch can only be replaced by an administrator's or an automation's stop.

calibration_abandon ends a pump's calibration session early, but only after its run has been reported. Before that, stop the pump first.

What a timed run is refused for, or stopped by

A run does not start while:

  • a dosing hardware fault is latched (CS-301);
  • a shot is in flight in the room;
  • a batch holds the room;
  • another hold is open (a feed probe session, or another pump's calibration or prime);
  • the room's kill switch is off.

A request older than 120 s is never acted on.

During the run, the controller checks every second and stops the run if the kill switch goes off, a hardware fault latches or a shot starts in the room.

What waits while a run is open

  • While a pump's calibration session is open, and for a prime's run plus 60 s after it is reported, dosing_config carries calibrating with that pump.
  • Doses and batches are refused: "A dosing pump is being calibrated or primed: doses and batches wait until it is done".
  • Primes and calibrations of other pumps are refused.
  • Shots are not held by an open pump session. A pump-only entry does not touch the source-water gate.

A run that went into the jug is not counted as stock drawn from the tank.

CS-801: a pump that does not read off

After every prime, calibration run or pump_stop, the controller switches the pump's power off and reads it back. If it does not read off at the end of a prime or calibration run the controller was running:

  • the run is reported as stuck (ran_s: 0) and journaled as dose.completed with outcome failed and code CS-801;
  • a critical alert is raised, CS-801 (Dosing pump overrun), titled "a dosing pump does not read off after a prime" or "… after a pump calibration";
  • the room is latched: its shots are held with the reason "dosing hardware still on: ", and new doses, batches and timed runs are refused;
  • the controller sends the off again every 2 s and repeats the alert every 30 minutes, titled "a dosing pump does not read off";
  • the latch is written to the controller's dosing_state.json, so it holds across a controller restart.

A pump_stop with no timed run going works differently. If the pump does not read off, the stop answers "stopped; does not read off" (not stuck), no dose.completed is journaled, and the alert is titled "a dosing pump does not read off after a stop". The room is latched the same way, but only in the controller's memory: nothing is written to dosing_state.json, so that latch does not survive a controller restart.

The latch clears by itself once the power reads off. Then the room waters again. The room Stop sends the off again too. If the pump is still running, switch it off by hand, and check the tank before the next dose.

Journal events

dose.completed (with data.kind prime or calibrate, seconds, ran_s and result: done, stopped, stuck or refused: …), pump.primed (journaled for every prime, with outcome refused or aborted when it did not run; the pump's primed_at is stamped only when ran_s is above 0) and pump.calibrated (with ml_min, previous_ml_min, run_s and measured_ml). The pump's history in sensor.crop_steering_<prefix>dosing lists each prime and calibration.

For the controller's side of a timed run, see DOSING.md › A timed run.