corrected autotrack still working when deactivated
change track to radio
This commit is contained in:
@@ -45,9 +45,6 @@ type DeviceManager struct {
|
||||
lastFreqUpdateTime time.Time // Last time we sent frequency update
|
||||
freqUpdateCooldown time.Duration // Minimum time between updates
|
||||
|
||||
// Cached Ultrabeam state for FlexRadio interlock (avoid mutex contention)
|
||||
ultrabeamMotorsMoving int
|
||||
ultrabeamStateMu sync.RWMutex
|
||||
}
|
||||
|
||||
type SystemStatus struct {
|
||||
@@ -67,7 +64,7 @@ func NewDeviceManager(cfg *config.Config, hub *Hub) *DeviceManager {
|
||||
return &DeviceManager{
|
||||
config: cfg,
|
||||
hub: hub,
|
||||
updateInterval: 1 * time.Second, // Update status every second
|
||||
updateInterval: 200 * time.Millisecond, // Update status every second
|
||||
stopChan: make(chan struct{}),
|
||||
freqThreshold: 25000, // 25 kHz default
|
||||
autoTrackEnabled: true, // Enabled by default
|
||||
@@ -89,12 +86,14 @@ func (dm *DeviceManager) Initialize() error {
|
||||
)
|
||||
|
||||
// Initialize Tuner Genius
|
||||
log.Printf("Initializing TunerGenius: host=%s port=%d", dm.config.Devices.TunerGenius.Host, dm.config.Devices.TunerGenius.Port)
|
||||
dm.tunerGenius = tunergenius.New(
|
||||
dm.config.Devices.TunerGenius.Host,
|
||||
dm.config.Devices.TunerGenius.Port,
|
||||
)
|
||||
|
||||
// Initialize Antenna Genius
|
||||
log.Printf("Initializing AntennaGenius: host=%s port=%d", dm.config.Devices.AntennaGenius.Host, dm.config.Devices.AntennaGenius.Port)
|
||||
dm.antennaGenius = antennagenius.New(
|
||||
dm.config.Devices.AntennaGenius.Host,
|
||||
dm.config.Devices.AntennaGenius.Port,
|
||||
@@ -123,18 +122,6 @@ func (dm *DeviceManager) Initialize() error {
|
||||
dm.config.Devices.FlexRadio.InterlockName,
|
||||
)
|
||||
|
||||
// Set callback to check if transmit is allowed (based on Ultrabeam motors)
|
||||
// Use cached state to avoid mutex contention with update loop
|
||||
dm.flexRadio.SetTransmitCheckCallback(func() bool {
|
||||
dm.ultrabeamStateMu.RLock()
|
||||
motorsMoving := dm.ultrabeamMotorsMoving
|
||||
dm.ultrabeamStateMu.RUnlock()
|
||||
// Block transmit if motors are moving
|
||||
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)
|
||||
dm.flexRadio.SetFrequencyChangeCallback(func(freqMHz float64) {
|
||||
dm.handleFrequencyChange(freqMHz)
|
||||
@@ -213,6 +200,11 @@ func (dm *DeviceManager) Start() error {
|
||||
// This provides instant auto-track response instead of waiting for updateStatus cycle
|
||||
func (dm *DeviceManager) handleFrequencyChange(freqMHz float64) {
|
||||
// Check if ultrabeam is initialized
|
||||
// Check if auto-track is enabled
|
||||
if !dm.autoTrackEnabled {
|
||||
return
|
||||
}
|
||||
|
||||
if dm.ultrabeam == nil {
|
||||
return
|
||||
}
|
||||
@@ -359,34 +351,6 @@ func (dm *DeviceManager) updateStatus() {
|
||||
dm.ultrabeamDirection = ubStatus.Direction
|
||||
}
|
||||
|
||||
// Cache motors state for FlexRadio interlock callback
|
||||
dm.ultrabeamStateMu.Lock()
|
||||
previousMotors := dm.ultrabeamMotorsMoving
|
||||
dm.ultrabeamMotorsMoving = ubStatus.MotorsMoving
|
||||
dm.ultrabeamStateMu.Unlock()
|
||||
|
||||
// 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 {
|
||||
log.Printf("Ultrabeam error: %v", err)
|
||||
}
|
||||
@@ -402,15 +366,12 @@ func (dm *DeviceManager) updateStatus() {
|
||||
}
|
||||
|
||||
// Auto frequency tracking: Update Ultrabeam when radio frequency differs from Ultrabeam
|
||||
// Priority: FlexRadio (fast) > TunerGenius (slow backup)
|
||||
if dm.autoTrackEnabled {
|
||||
// TunerGenius tracking (FlexRadio uses immediate callback)
|
||||
var radioFreqKhz int
|
||||
var radioSource string
|
||||
|
||||
if dm.flexRadio != nil && status.FlexRadio != nil && status.FlexRadio.Connected && status.FlexRadio.Frequency > 0 {
|
||||
// Use FlexRadio frequency (in MHz, convert to kHz)
|
||||
radioFreqKhz = int(status.FlexRadio.Frequency * 1000)
|
||||
radioSource = "FlexRadio"
|
||||
} else if dm.autoTrackEnabled && status.TunerGenius != nil && status.TunerGenius.Connected {
|
||||
if status.TunerGenius != nil && status.TunerGenius.Connected {
|
||||
// Fallback to TunerGenius frequency (already in kHz)
|
||||
radioFreqKhz = int(status.TunerGenius.FreqA)
|
||||
radioSource = "TunerGenius"
|
||||
@@ -484,6 +445,7 @@ func (dm *DeviceManager) updateStatus() {
|
||||
dm.hub.BroadcastStatusUpdate(status)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (dm *DeviceManager) GetStatus() *SystemStatus {
|
||||
|
||||
@@ -20,10 +20,6 @@ type Client struct {
|
||||
connMu sync.Mutex // For connection management
|
||||
writeMu sync.Mutex // For writing to connection (separate from reads)
|
||||
|
||||
interlockID string
|
||||
interlockName string
|
||||
interlockMu sync.RWMutex
|
||||
|
||||
lastStatus *Status
|
||||
statusMu sync.RWMutex
|
||||
|
||||
@@ -34,7 +30,6 @@ type Client struct {
|
||||
stopChan chan struct{}
|
||||
|
||||
// Callbacks
|
||||
checkTransmitAllowed func() bool
|
||||
onFrequencyChange func(freqMHz float64)
|
||||
}
|
||||
|
||||
@@ -42,7 +37,6 @@ func New(host string, port int, interlockName string) *Client {
|
||||
return &Client{
|
||||
host: host,
|
||||
port: port,
|
||||
interlockName: interlockName,
|
||||
stopChan: make(chan struct{}),
|
||||
lastStatus: &Status{
|
||||
Connected: false,
|
||||
@@ -50,11 +44,6 @@ func New(host string, port int, interlockName string) *Client {
|
||||
}
|
||||
}
|
||||
|
||||
// SetTransmitCheckCallback sets the callback function to check if transmit is allowed
|
||||
func (c *Client) SetTransmitCheckCallback(callback func() bool) {
|
||||
c.checkTransmitAllowed = callback
|
||||
}
|
||||
|
||||
// SetFrequencyChangeCallback sets the callback function called when frequency changes
|
||||
func (c *Client) SetFrequencyChangeCallback(callback func(freqMHz float64)) {
|
||||
c.onFrequencyChange = callback
|
||||
@@ -104,10 +93,11 @@ func (c *Client) Start() error {
|
||||
// Start message listener
|
||||
go c.messageLoop()
|
||||
|
||||
// Create interlock (no sleep needed, connection is synchronous)
|
||||
if err := c.createInterlock(); err != nil {
|
||||
log.Printf("FlexRadio: Failed to create interlock: %v", err)
|
||||
return err
|
||||
// Subscribe to slice updates for frequency tracking
|
||||
log.Println("FlexRadio: Subscribing to slice updates...")
|
||||
_, err := c.sendCommand("sub slice all")
|
||||
if err != nil {
|
||||
log.Printf("FlexRadio: Warning - failed to subscribe to slices: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -223,7 +213,6 @@ func (c *Client) messageLoop() {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("FlexRadio RX: %s", line)
|
||||
c.handleMessage(line)
|
||||
}
|
||||
|
||||
@@ -233,7 +222,6 @@ func (c *Client) messageLoop() {
|
||||
func (c *Client) handleMessage(msg string) {
|
||||
// Response format: R<seq>|<status>|<data>
|
||||
if strings.HasPrefix(msg, "R") {
|
||||
c.handleResponse(msg)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -256,36 +244,6 @@ func (c *Client) handleMessage(msg string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) handleResponse(msg string) {
|
||||
// Format: R<seq>|<status>|<data>
|
||||
parts := strings.SplitN(msg, "|", 3)
|
||||
if len(parts) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
status := parts[1]
|
||||
if status != "0" {
|
||||
log.Printf("FlexRadio: Command error: status=%s", status)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if this is interlock create response
|
||||
if len(parts) >= 3 && parts[2] != "" {
|
||||
// This is likely the interlock ID
|
||||
interlockID := parts[2]
|
||||
c.interlockMu.Lock()
|
||||
c.interlockID = interlockID
|
||||
c.interlockMu.Unlock()
|
||||
|
||||
log.Printf("FlexRadio: Interlock created with ID: %s", interlockID)
|
||||
|
||||
c.statusMu.Lock()
|
||||
c.lastStatus.InterlockID = interlockID
|
||||
c.lastStatus.InterlockState = InterlockStateReady
|
||||
c.statusMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) handleStatus(msg string) {
|
||||
// Format: S<handle>|<key>=<value> ...
|
||||
parts := strings.SplitN(msg, "|", 2)
|
||||
@@ -306,10 +264,6 @@ func (c *Client) handleStatus(msg string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check for interlock state changes
|
||||
if state, ok := statusMap["state"]; ok && strings.Contains(msg, "interlock") {
|
||||
c.handleInterlockState(state, statusMap)
|
||||
}
|
||||
// Check for slice updates (frequency changes)
|
||||
if strings.Contains(msg, "slice") {
|
||||
if rfFreq, ok := statusMap["RF_frequency"]; ok {
|
||||
@@ -334,75 +288,6 @@ func (c *Client) handleStatus(msg string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) handleInterlockState(state string, statusMap map[string]string) {
|
||||
log.Printf("FlexRadio: Interlock state changed to: %s", state)
|
||||
|
||||
c.statusMu.Lock()
|
||||
c.lastStatus.InterlockState = state
|
||||
c.statusMu.Unlock()
|
||||
|
||||
// Handle PTT_REQUESTED - this is where we decide to allow or block transmit
|
||||
if state == "PTT_REQUESTED" {
|
||||
c.handlePTTRequest()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) handlePTTRequest() {
|
||||
log.Println("FlexRadio: PTT requested, checking if transmit is allowed...")
|
||||
|
||||
c.interlockMu.RLock()
|
||||
interlockID := c.interlockID
|
||||
c.interlockMu.RUnlock()
|
||||
|
||||
if interlockID == "" {
|
||||
log.Println("FlexRadio: No interlock ID, cannot respond to PTT request")
|
||||
return
|
||||
}
|
||||
|
||||
// Check if transmit is allowed via callback
|
||||
allowed := true
|
||||
if c.checkTransmitAllowed != nil {
|
||||
allowed = c.checkTransmitAllowed()
|
||||
}
|
||||
|
||||
if allowed {
|
||||
log.Println("FlexRadio: Transmit ALLOWED - sending ready")
|
||||
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: Transmit BLOCKED - sending not_ready")
|
||||
c.sendCommand(fmt.Sprintf("interlock not_ready %s", interlockID))
|
||||
// Update state immediately for UI
|
||||
c.statusMu.Lock()
|
||||
c.lastStatus.InterlockState = InterlockStateNotReady
|
||||
c.statusMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) createInterlock() error {
|
||||
log.Printf("FlexRadio: Creating interlock with name: %s", c.interlockName)
|
||||
|
||||
// Format: interlock create type=ant name=<name> serial=<serial>
|
||||
cmd := fmt.Sprintf("interlock create type=ant name=%s serial=ShackMaster", c.interlockName)
|
||||
|
||||
_, err := c.sendCommand(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create interlock: %w", err)
|
||||
}
|
||||
|
||||
// Subscribe to slice updates for frequency tracking
|
||||
log.Println("FlexRadio: Subscribing to slice updates...")
|
||||
_, err = c.sendCommand("sub slice all")
|
||||
if err != nil {
|
||||
log.Printf("FlexRadio: Warning - failed to subscribe to slices: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) GetStatus() (*Status, error) {
|
||||
c.statusMu.RLock()
|
||||
defer c.statusMu.RUnlock()
|
||||
@@ -419,32 +304,3 @@ 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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
const newHeading = status.heading;
|
||||
const oldHeading = heading;
|
||||
|
||||
console.log(`RotatorGenius heading update: ${oldHeading} -> ${newHeading}`);
|
||||
|
||||
if (heading === null) {
|
||||
// First time: accept any value
|
||||
heading = newHeading;
|
||||
@@ -56,7 +54,7 @@
|
||||
try {
|
||||
hasTarget = true; // Mark that we have a target
|
||||
// Subtract 10 degrees to compensate for rotator momentum
|
||||
const adjustedHeading = (targetHeading - 10 + 360) % 360;
|
||||
const adjustedHeading = (targetHeading + 360) % 360;
|
||||
await api.rotator.setHeading(adjustedHeading);
|
||||
} catch (err) {
|
||||
console.error('Failed to set heading:', err);
|
||||
|
||||
@@ -191,7 +191,7 @@
|
||||
<div class="auto-track-controls">
|
||||
<label class="toggle-label">
|
||||
<input type="checkbox" bind:checked={autoTrackEnabled} on:change={updateAutoTrack} />
|
||||
<span>Enable Auto-Track from Tuner</span>
|
||||
<span>Enable Auto-Track from Radio</span>
|
||||
</label>
|
||||
|
||||
<div class="threshold-group">
|
||||
|
||||
Reference in New Issue
Block a user