feat(steppir): a Calibrate button, and Retract explains itself

Calibrate was in the protocol notes at the top of the package ('V') and nowhere
else. It drives every element to its end stop so the controller re-learns where
zero is, and it is the cure for the one SteppIR fault an operator cannot reason
about: an antenna that tunes to the wrong length. The controller counts steps
from a remembered position, so once that memory is wrong — a power cut in
mid-move, elements pushed by hand, a motor that slipped — every frequency after
it is wrong by the same amount, silently.

It asks before it runs. Minutes, every element travelling its full length, and
the antenna unusable until it finishes: right answer to a mistuned beam, wrong
answer to a stray click during a contest.

It sits beside Retract as a two-column row rather than a second full-width
button. The docked widget is narrow and already tall, and the pair reads as what
it is — the two commands that move every element at once.

Retract (Home) was already there and worked on a SteppIR; it now carries a
tooltip saying what it is for, because "retract elements" does not tell an
operator that this is the storage position and that the next tune brings them
back out unaided.

An Ultrabeam controller has no calibration command, and the adapter says so
instead of pretending — the antenna interface is shared, and a button that
silently does nothing is worse than one that explains itself.

Untested on hardware: the byte is from SteppIR's own Transceiver Interface note,
which is the same source the rest of this package was cross-checked against.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
2026-09-10 08:00:22 +02:00
co-authored by Claude Opus 5
parent ce7b3686f5
commit a88e871640
7 changed files with 99 additions and 14 deletions
+25
View File
@@ -741,3 +741,28 @@ func portBusyHint(mode, com string, err error) string {
}
return " — another program already has " + com + " open (the SteppIR control window, PstRotator, a terminal). A COM port has one owner: close the other program, then OpsLog can connect."
}
// Calibrate runs the controller's calibration: the elements are driven to their
// end stops so the controller re-learns where zero is.
//
// It is the cure for an antenna that tunes to the wrong length after a power cut
// mid-move, after the elements have been retracted by hand, or after a motor has
// slipped — the controller counts steps from a remembered position, and once
// that memory is wrong every frequency after it is wrong by the same amount.
//
// It takes MINUTES and moves every element the whole way, so it is not something
// to do during a contest. Like Retract it drops the controller out of AUTOTRACK,
// which is handled transparently: the next SetFrequency re-issues AUTOTRACK ON.
func (c *Client) Calibrate() error {
// A valid frequency accompanies every SET frame; the controller ignores it
// for this command, but a malformed frame is refused outright.
khz := c.LastSetKHz()
if khz <= 0 {
if st, _ := c.GetStatus(); st != nil && st.Frequency > 0 {
khz = st.Frequency
} else {
khz = 14000
}
}
return c.writeCmd(buildSet(khz*1000, DirNormal, 'V'))
}