Twenty-eight hours of flat pH, and the number the loop could not learn
Yesterday's post ended with the new dosing loop switched on at 14:14. This is what the first twenty-eight hours look like, what the loop learned, what it could not learn, and the two changes that went to the greenhouse controller tonight because of it.
The numbers
The day before the switch, pH fell from 5.89 to 5.59, a quarter of a unit, because this fertiliser recipe pushes it down and nothing was pushing back except me. In the twenty-eight hours since, the one-minute readings have all been between 5.82 and 5.87, standard deviation 0.007 pH. That is one minimum shot wide, which is what the design said it would be: the loop fires 2 ml whenever its running estimate says the tank is about 0.03 short, and 2 ml moves the tank about 0.03. EC held 1300 to 1310 against a target of 1300, standard deviation 3 µS/cm. Twice today the float valve added three and a half litres of rainwater and EC dropped to 1280; both times the loop had it back above 1295 within four minutes of the drop, because it fired the moment the reading came in rather than waiting for the next scheduled decision.
The loop also learned. The A/B gain it started with, 14 µS per 50 ml of each part, was measured on the old liquid fertiliser; after nine shots it had moved the stored value to about 20 µS per 50 ml, which is what the dry-salt mix actually does. Individual shots are noisy, a 16 ml shot moves the reading by five to eight counts on a one-count meter, but the two larger shots it fired after the top-ups landed on the same number as the average, so I believe it. The EC drift it learned, about 70 µS per day of plant uptake, matches the daily A/B total to within a few per cent.
The number it could not learn
The pH-up pump's gain never changed from its starting value, and it took me an hour of staring at the event log to see why. The loop only trusts a shot as a measurement of the pump when the expected movement is at least 0.04 pH, four counts, because the probe's last digit is noise. But the loop, doing its job well, never needed more than 2 ml at a time, and 2 ml is expected to move 0.026. Every shot was below the threshold. Meanwhile the drift estimator saw "pump fired, reading did not move" and booked the whole thing to drift, which crept from the seed value of 0.15 pH per day to 0.43 per day and was still rising when I looked.
The control result is fine either way; the tank does not care which of the two numbers is right as long as their ratio is. But the two are not separable from minimum-sized shots alone, and the wrong split has a real consequence: I told you yesterday the litre of working solution would last half a year at 12 ml a day. The pump has actually delivered 35 ml a day, so it is two months. Either nitrification really did speed up when pH went from 5.6 to 5.85, which is plausible, or a two-second pump pulse delivers less than the calibration says, which is also plausible. I do not know yet, and the loop as written would never find out.
Two changes, pushed tonight
The first is a probe shot. When five consecutive pH shots have been too small to learn from, the next one that was going to fire anyway is enlarged to the smallest size the learner accepts, 4 ml at the current gain. No extra shots, one slightly bigger one every seven hours or so, an overshoot of 0.03 at most, and in return a real measurement of the pump. The first one should land tomorrow morning.
The second closes the top-up gap properly. The controller already counts the water meter on the fill line. From tonight, every 0.1 litre the float valve lets in is converted into an EC debt, volume times target divided by tank volume, and paid back through the learned A/B gain as soon as it adds up to a minimum shot and the pumps are free. A three-and-a-half-litre fill owes about 70 ml of each part; the pumps start on it while the valve is still open and finish in about three minutes, instead of the loop noticing a dip and chasing it afterwards. The main loop holds its decision while the valve is open and restarts its settling clock after each compensation shot, so the two cannot both pay the same debt. The tank volume became a setting; the daily A/B cap went up because the compensation rides the same counter.
Honesty section
Twenty-eight hours is twenty-eight hours. The flat line is real and it is the best this reservoir has ever looked, but the learned drift is a number I have just told you is probably wrong, and the top-up code has not seen a top-up yet. The learned values survived tonight's firmware swap and reboot intact, which was the one thing about persistence I had not yet tested. I will report the first probe shot and the first automatic fill compensation when they happen, including if they do something silly.