fix: drain SPE status backlog so display is current (was ~15s stale); ON now pulses DTR (0x0B is level-only); power-level cycle waits for real status change
This commit is contained in:
+62
-8
@@ -146,30 +146,53 @@ func (c *Client) Operate(on bool) error {
|
||||
// ToggleOperate flips STANDBY/OPERATE unconditionally.
|
||||
func (c *Client) ToggleOperate() error { return c.sendCmd(cmdOperate) }
|
||||
|
||||
// PowerOn presses the POWER key (turns the amp on when it's off).
|
||||
func (c *Client) PowerOn() error { return c.sendCmd(cmdPower) }
|
||||
// PowerOn turns the amplifier on. Per the SPE manual this is NOT a data command
|
||||
// but a hardware DTR pulse on the serial line (the "Remote_ON" pin), which needs
|
||||
// the special SPE cable — so it only applies to the serial transport. Over TCP
|
||||
// there is no DTR line and power-on isn't possible remotely.
|
||||
func (c *Client) PowerOn() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
sp, ok := c.conn.(serial.Port)
|
||||
if !ok {
|
||||
return fmt.Errorf("power-on needs the serial DTR line (special SPE cable); not available over network")
|
||||
}
|
||||
// A single positive pulse (~1.2s) on DTR, as the manual describes.
|
||||
if err := sp.SetDTR(true); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(1200 * time.Millisecond)
|
||||
return sp.SetDTR(false)
|
||||
}
|
||||
|
||||
// PowerOff presses the OFF key (switches the amp off).
|
||||
func (c *Client) PowerOff() error { return c.sendCmd(cmdOff) }
|
||||
|
||||
// SetPowerLevel cycles the output power level (L/M/H) to the requested one by
|
||||
// tapping the POWER key until the reported level matches, giving the amp time to
|
||||
// report each change. `level` is "L", "M" or "H" (case-insensitive).
|
||||
// tapping the POWER key (0x0B) and WAITING for the amp to actually report the new
|
||||
// level before the next tap — the status is streamed, so we poll GetStatus rather
|
||||
// than sleeping a fixed time. `level` is "L", "M" or "H" (case-insensitive).
|
||||
func (c *Client) SetPowerLevel(level string) error {
|
||||
want := strings.ToUpper(strings.TrimSpace(level))
|
||||
if want == "" {
|
||||
return nil
|
||||
}
|
||||
// At most 3 taps to walk the 3-way cycle back around to the target.
|
||||
// At most 3 taps to walk the 3-way L→M→H cycle around to the target.
|
||||
for i := 0; i < 3; i++ {
|
||||
cur := strings.ToUpper(strings.TrimSpace(c.GetStatus().PowerLevel))
|
||||
if cur == want {
|
||||
if strings.ToUpper(strings.TrimSpace(c.GetStatus().PowerLevel)) == want {
|
||||
return nil
|
||||
}
|
||||
prev := strings.ToUpper(strings.TrimSpace(c.GetStatus().PowerLevel))
|
||||
if err := c.sendCmd(cmdPower); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(1 * time.Second) // let the amp apply it and the poll refresh status
|
||||
// Wait (up to ~2s) for the streamed status to reflect the change.
|
||||
for w := 0; w < 20; w++ {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
if strings.ToUpper(strings.TrimSpace(c.GetStatus().PowerLevel)) != prev {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -186,6 +209,11 @@ func (c *Client) pollLoop() {
|
||||
c.setErr(fmt.Errorf("connect: %w", err))
|
||||
continue
|
||||
}
|
||||
// The amp streams status frames faster than one per poll, so a backlog
|
||||
// builds up and we'd read stale frames (the display lagged ~15s behind
|
||||
// the real amp). Flush anything pending, THEN request + read exactly one
|
||||
// fresh frame — the status we show is always current.
|
||||
c.drainInput()
|
||||
if err := c.sendCmd(cmdStatus); err != nil {
|
||||
c.mu.Lock()
|
||||
c.dropLocked()
|
||||
@@ -229,6 +257,32 @@ func (c *Client) dropLocked() {
|
||||
}
|
||||
}
|
||||
|
||||
// drainInput discards everything currently pending on the link (OS buffer + the
|
||||
// bufio reader) so the next read returns a fresh frame rather than a queued stale
|
||||
// one. Called before each status request.
|
||||
func (c *Client) drainInput() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.conn == nil {
|
||||
return
|
||||
}
|
||||
if sp, ok := c.conn.(serial.Port); ok {
|
||||
_ = sp.ResetInputBuffer()
|
||||
} else if nc, ok := c.conn.(net.Conn); ok {
|
||||
_ = nc.SetReadDeadline(time.Now().Add(15 * time.Millisecond))
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
n, err := nc.Read(buf)
|
||||
if n == 0 || err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.r != nil {
|
||||
c.r.Reset(c.conn)
|
||||
}
|
||||
}
|
||||
|
||||
// sendCmd frames one keystroke code and writes it. Single-byte payload → CHK is
|
||||
// the code itself.
|
||||
func (c *Client) sendCmd(code byte) error {
|
||||
|
||||
Reference in New Issue
Block a user