fix: bug on steppir control

This commit is contained in:
2026-07-16 00:42:16 +02:00
parent f650183936
commit 9d7091b1b8
3 changed files with 52 additions and 24 deletions
+26 -20
View File
@@ -74,6 +74,12 @@ type Client struct {
connMu sync.Mutex
conn io.ReadWriteCloser
// ioMu serialises EVERY exchange on the shared connection — a status query
// (write "?A" then read 11 bytes) and a command write must never interleave,
// or their bytes mix on the wire and both frames are corrupted. The status
// poll runs on one goroutine, tuning on another, so this is essential.
ioMu sync.Mutex
statusMu sync.RWMutex
lastStatus *Status
lastSetKHz int
@@ -85,11 +91,6 @@ type Client struct {
pendingDirAt time.Time
pendingDirSet bool
// After a Home/Retract the controller drops out of AUTOTRACK and ignores
// frequency sets until it is turned back ON. Set on Retract, cleared by
// re-enabling on the next SetFrequency.
needAutotrack bool
stopChan chan struct{}
running bool
}
@@ -231,6 +232,8 @@ func (c *Client) queryStatus() (*Status, error) {
if conn == nil {
return nil, fmt.Errorf("steppir: not connected")
}
c.ioMu.Lock()
defer c.ioMu.Unlock()
setDeadline(conn, 3*time.Second)
if _, err := conn.Write([]byte("?A\r")); err != nil {
return nil, fmt.Errorf("write status cmd: %w", err)
@@ -299,6 +302,9 @@ func (c *Client) writeCmd(pkt []byte) error {
if conn == nil {
return fmt.Errorf("steppir: not connected")
}
c.ioMu.Lock()
defer c.ioMu.Unlock()
log.Printf("steppir: → % X", pkt)
setDeadline(conn, 3*time.Second)
if _, err := conn.Write(pkt); err != nil {
c.closeConn()
@@ -309,16 +315,19 @@ func (c *Client) writeCmd(pkt []byte) error {
return nil
}
// SetFrequency tunes the elements to freqKhz with the given direction. If a prior
// Retract dropped AUTOTRACK, re-enable it first — otherwise the set is ignored.
// SetFrequency tunes the elements to freqKhz with the given direction.
//
// AUTOTRACK is (re-)enabled first, EVERY time: the controller ignores frequency
// sets unless it is in AUTOTRACK mode ("when not in AUTOTRACK only CALIBRATE and
// RETRACT work"), and it can be out of AUTOTRACK at power-on, after a Home, or if
// switched off on the front panel. Sending the 'R' command each tune is cheap and
// makes tuning work regardless of the controller's current mode — which is what
// was silently failing before.
func (c *Client) SetFrequency(freqKhz int, direction int) error {
if c.needAutotrack {
if err := c.writeCmd(buildSet(freqKhz*1000, direction, 'R')); err != nil {
return err
}
c.needAutotrack = false
if err := c.writeCmd(buildSet(freqKhz*1000, direction, 'R')); err != nil { // AUTOTRACK ON
return err
}
if err := c.writeCmd(buildSet(freqKhz*1000, direction, '1')); err != nil {
if err := c.writeCmd(buildSet(freqKhz*1000, direction, '1')); err != nil { // set freq + dir
return err
}
c.statusMu.Lock()
@@ -343,8 +352,9 @@ func (c *Client) SetDirection(direction int) error {
return c.SetFrequency(khz, direction)
}
// Retract homes the elements into the hubs (storage). This leaves AUTOTRACK off,
// so the next SetFrequency re-enables it.
// Retract homes the elements into the hubs (storage). This drops the controller
// out of AUTOTRACK, but that is handled transparently: the next SetFrequency
// re-issues AUTOTRACK ON before tuning.
func (c *Client) Retract() error {
// A valid frequency must accompany the command; reuse the last one.
khz := c.LastSetKHz()
@@ -355,9 +365,5 @@ func (c *Client) Retract() error {
khz = 14000 // any in-range value; the controller just homes
}
}
if err := c.writeCmd(buildSet(khz*1000, DirNormal, 'S')); err != nil {
return err
}
c.needAutotrack = true
return nil
return c.writeCmd(buildSet(khz*1000, DirNormal, 'S'))
}