diff --git a/internal/api/device_manager.go b/internal/api/device_manager.go index 6b6ccea..9f50bd4 100644 --- a/internal/api/device_manager.go +++ b/internal/api/device_manager.go @@ -67,7 +67,7 @@ func NewDeviceManager(cfg *config.Config, hub *Hub) *DeviceManager { return &DeviceManager{ config: cfg, hub: hub, - updateInterval: 200 * time.Millisecond, // Update status every second + updateInterval: 1 * time.Second, // Update status every second stopChan: make(chan struct{}), freqThreshold: 25000, // 25 kHz default autoTrackEnabled: true, // Enabled by default @@ -130,7 +130,9 @@ func (dm *DeviceManager) Initialize() error { motorsMoving := dm.ultrabeamMotorsMoving dm.ultrabeamStateMu.RUnlock() // Block transmit if motors are moving - return motorsMoving == 0 + allowed := motorsMoving == 0 + log.Printf("FlexRadio PTT check: motorsMoving=%d, transmit=%v", motorsMoving, allowed) + return allowed }) // Set callback for immediate frequency changes (no waiting for update cycle) @@ -363,12 +365,26 @@ func (dm *DeviceManager) updateStatus() { dm.ultrabeamMotorsMoving = ubStatus.MotorsMoving dm.ultrabeamStateMu.Unlock() - // Log motor state changes + // Proactively update FlexRadio interlock when motor state changes if previousMotors != ubStatus.MotorsMoving { if ubStatus.MotorsMoving > 0 { log.Printf("Ultrabeam: Motors STARTED (bitmask=%d)", ubStatus.MotorsMoving) + // PROACTIVELY block transmit - don't wait for PTT_REQUESTED + log.Printf("DEBUG: About to call ForceInterlockState(false), flexRadio=%v", dm.flexRadio != nil) + if dm.flexRadio != nil { + dm.flexRadio.ForceInterlockState(false) + } else { + log.Printf("DEBUG: FlexRadio is nil, cannot force interlock") + } } else { log.Printf("Ultrabeam: Motors STOPPED") + // PROACTIVELY allow transmit again + log.Printf("DEBUG: About to call ForceInterlockState(true), flexRadio=%v", dm.flexRadio != nil) + if dm.flexRadio != nil { + dm.flexRadio.ForceInterlockState(true) + } else { + log.Printf("DEBUG: FlexRadio is nil, cannot force interlock") + } } } } else { @@ -467,6 +483,7 @@ func (dm *DeviceManager) updateStatus() { if dm.hub != nil { dm.hub.BroadcastStatusUpdate(status) } + } func (dm *DeviceManager) GetStatus() *SystemStatus { diff --git a/internal/devices/flexradio/flexradio.go b/internal/devices/flexradio/flexradio.go index d014a8f..325a5f6 100644 --- a/internal/devices/flexradio/flexradio.go +++ b/internal/devices/flexradio/flexradio.go @@ -15,9 +15,10 @@ type Client struct { host string port int - conn net.Conn - reader *bufio.Reader - connMu sync.Mutex + conn net.Conn + reader *bufio.Reader + connMu sync.Mutex // For connection management + writeMu sync.Mutex // For writing to connection (separate from reads) interlockID string interlockName string @@ -144,10 +145,15 @@ func (c *Client) getNextSeq() int { } func (c *Client) sendCommand(cmd string) (string, error) { - c.connMu.Lock() - defer c.connMu.Unlock() + // Use writeMu instead of connMu to avoid blocking on messageLoop reads + c.writeMu.Lock() + defer c.writeMu.Unlock() - if c.conn == nil { + c.connMu.Lock() + conn := c.conn + c.connMu.Unlock() + + if conn == nil { return "", fmt.Errorf("not connected") } @@ -156,10 +162,12 @@ func (c *Client) sendCommand(cmd string) (string, error) { log.Printf("FlexRadio TX: %s", strings.TrimSpace(fullCmd)) - _, err := c.conn.Write([]byte(fullCmd)) + _, err := conn.Write([]byte(fullCmd)) if err != nil { + c.connMu.Lock() c.conn = nil c.reader = nil + c.connMu.Unlock() return "", fmt.Errorf("failed to send command: %w", err) } @@ -215,6 +223,7 @@ func (c *Client) messageLoop() { continue } + log.Printf("FlexRadio RX: %s", line) c.handleMessage(line) } @@ -325,7 +334,7 @@ func (c *Client) handleStatus(msg string) { } } -func (c *Client) handleInterlockState(state string, _ map[string]string) { +func (c *Client) handleInterlockState(state string, statusMap map[string]string) { log.Printf("FlexRadio: Interlock state changed to: %s", state) c.statusMu.Lock() @@ -410,3 +419,32 @@ func (c *Client) GetStatus() (*Status, error) { return &status, nil } + +// ForceInterlockState proactively sends ready/not_ready to the radio +// This is used when external conditions change (e.g., antenna motors start/stop) +func (c *Client) ForceInterlockState(allowed bool) { + c.interlockMu.RLock() + interlockID := c.interlockID + c.interlockMu.RUnlock() + + if interlockID == "" { + log.Println("FlexRadio: No interlock ID, cannot force state") + return + } + + if allowed { + log.Println("FlexRadio: PROACTIVE - Sending ready (motors stopped)") + c.sendCommand(fmt.Sprintf("interlock ready %s", interlockID)) + // Update state immediately for UI + c.statusMu.Lock() + c.lastStatus.InterlockState = InterlockStateReady + c.statusMu.Unlock() + } else { + log.Println("FlexRadio: PROACTIVE - Sending not_ready (motors moving)") + c.sendCommand(fmt.Sprintf("interlock not_ready %s", interlockID)) + // Update state immediately for UI + c.statusMu.Lock() + c.lastStatus.InterlockState = InterlockStateNotReady + c.statusMu.Unlock() + } +}