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
|
lastFreqUpdateTime time.Time // Last time we sent frequency update
|
||||||
freqUpdateCooldown time.Duration // Minimum time between updates
|
freqUpdateCooldown time.Duration // Minimum time between updates
|
||||||
|
|
||||||
// Cached Ultrabeam state for FlexRadio interlock (avoid mutex contention)
|
|
||||||
ultrabeamMotorsMoving int
|
|
||||||
ultrabeamStateMu sync.RWMutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SystemStatus struct {
|
type SystemStatus struct {
|
||||||
@@ -67,7 +64,7 @@ func NewDeviceManager(cfg *config.Config, hub *Hub) *DeviceManager {
|
|||||||
return &DeviceManager{
|
return &DeviceManager{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
hub: hub,
|
hub: hub,
|
||||||
updateInterval: 1 * time.Second, // Update status every second
|
updateInterval: 200 * time.Millisecond, // Update status every second
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
freqThreshold: 25000, // 25 kHz default
|
freqThreshold: 25000, // 25 kHz default
|
||||||
autoTrackEnabled: true, // Enabled by default
|
autoTrackEnabled: true, // Enabled by default
|
||||||
@@ -89,12 +86,14 @@ func (dm *DeviceManager) Initialize() error {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Initialize Tuner Genius
|
// 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.tunerGenius = tunergenius.New(
|
||||||
dm.config.Devices.TunerGenius.Host,
|
dm.config.Devices.TunerGenius.Host,
|
||||||
dm.config.Devices.TunerGenius.Port,
|
dm.config.Devices.TunerGenius.Port,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Initialize Antenna Genius
|
// 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.antennaGenius = antennagenius.New(
|
||||||
dm.config.Devices.AntennaGenius.Host,
|
dm.config.Devices.AntennaGenius.Host,
|
||||||
dm.config.Devices.AntennaGenius.Port,
|
dm.config.Devices.AntennaGenius.Port,
|
||||||
@@ -123,18 +122,6 @@ func (dm *DeviceManager) Initialize() error {
|
|||||||
dm.config.Devices.FlexRadio.InterlockName,
|
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)
|
// Set callback for immediate frequency changes (no waiting for update cycle)
|
||||||
dm.flexRadio.SetFrequencyChangeCallback(func(freqMHz float64) {
|
dm.flexRadio.SetFrequencyChangeCallback(func(freqMHz float64) {
|
||||||
dm.handleFrequencyChange(freqMHz)
|
dm.handleFrequencyChange(freqMHz)
|
||||||
@@ -213,6 +200,11 @@ func (dm *DeviceManager) Start() error {
|
|||||||
// This provides instant auto-track response instead of waiting for updateStatus cycle
|
// This provides instant auto-track response instead of waiting for updateStatus cycle
|
||||||
func (dm *DeviceManager) handleFrequencyChange(freqMHz float64) {
|
func (dm *DeviceManager) handleFrequencyChange(freqMHz float64) {
|
||||||
// Check if ultrabeam is initialized
|
// Check if ultrabeam is initialized
|
||||||
|
// Check if auto-track is enabled
|
||||||
|
if !dm.autoTrackEnabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if dm.ultrabeam == nil {
|
if dm.ultrabeam == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -359,34 +351,6 @@ func (dm *DeviceManager) updateStatus() {
|
|||||||
dm.ultrabeamDirection = ubStatus.Direction
|
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 {
|
} else {
|
||||||
log.Printf("Ultrabeam error: %v", err)
|
log.Printf("Ultrabeam error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -402,88 +366,86 @@ func (dm *DeviceManager) updateStatus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Auto frequency tracking: Update Ultrabeam when radio frequency differs from Ultrabeam
|
// Auto frequency tracking: Update Ultrabeam when radio frequency differs from Ultrabeam
|
||||||
// Priority: FlexRadio (fast) > TunerGenius (slow backup)
|
if dm.autoTrackEnabled {
|
||||||
var radioFreqKhz int
|
// TunerGenius tracking (FlexRadio uses immediate callback)
|
||||||
var radioSource string
|
var radioFreqKhz int
|
||||||
|
var radioSource string
|
||||||
|
|
||||||
if dm.flexRadio != nil && status.FlexRadio != nil && status.FlexRadio.Connected && status.FlexRadio.Frequency > 0 {
|
if status.TunerGenius != nil && status.TunerGenius.Connected {
|
||||||
// Use FlexRadio frequency (in MHz, convert to kHz)
|
// Fallback to TunerGenius frequency (already in kHz)
|
||||||
radioFreqKhz = int(status.FlexRadio.Frequency * 1000)
|
radioFreqKhz = int(status.TunerGenius.FreqA)
|
||||||
radioSource = "FlexRadio"
|
radioSource = "TunerGenius"
|
||||||
} else if dm.autoTrackEnabled && status.TunerGenius != nil && status.TunerGenius.Connected {
|
}
|
||||||
// Fallback to TunerGenius frequency (already in kHz)
|
|
||||||
radioFreqKhz = int(status.TunerGenius.FreqA)
|
|
||||||
radioSource = "TunerGenius"
|
|
||||||
}
|
|
||||||
|
|
||||||
if radioFreqKhz > 0 && status.Ultrabeam != nil && status.Ultrabeam.Connected {
|
if radioFreqKhz > 0 && status.Ultrabeam != nil && status.Ultrabeam.Connected {
|
||||||
ultrabeamFreqKhz := status.Ultrabeam.Frequency // Ultrabeam frequency in kHz
|
ultrabeamFreqKhz := status.Ultrabeam.Frequency // Ultrabeam frequency in kHz
|
||||||
|
|
||||||
// Only do auto-track if frequency is in Ultrabeam range (40M-6M: 7000-54000 kHz)
|
// Only do auto-track if frequency is in Ultrabeam range (40M-6M: 7000-54000 kHz)
|
||||||
// This prevents retraction when slice is closed (FreqA becomes 0) or on out-of-range bands
|
// This prevents retraction when slice is closed (FreqA becomes 0) or on out-of-range bands
|
||||||
if radioFreqKhz >= 7000 && radioFreqKhz <= 54000 {
|
if radioFreqKhz >= 7000 && radioFreqKhz <= 54000 {
|
||||||
freqDiff := radioFreqKhz - ultrabeamFreqKhz
|
freqDiff := radioFreqKhz - ultrabeamFreqKhz
|
||||||
if freqDiff < 0 {
|
if freqDiff < 0 {
|
||||||
freqDiff = -freqDiff
|
freqDiff = -freqDiff
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert diff to Hz for comparison with threshold (which is in Hz)
|
// Convert diff to Hz for comparison with threshold (which is in Hz)
|
||||||
freqDiffHz := freqDiff * 1000
|
freqDiffHz := freqDiff * 1000
|
||||||
|
|
||||||
// Don't send command if motors are already moving
|
// Don't send command if motors are already moving
|
||||||
if status.Ultrabeam.MotorsMoving == 0 {
|
if status.Ultrabeam.MotorsMoving == 0 {
|
||||||
if freqDiffHz >= dm.freqThreshold {
|
if freqDiffHz >= dm.freqThreshold {
|
||||||
// Use user's explicitly set direction, or fallback to current Ultrabeam direction
|
// Use user's explicitly set direction, or fallback to current Ultrabeam direction
|
||||||
directionToUse := dm.ultrabeamDirection
|
directionToUse := dm.ultrabeamDirection
|
||||||
if !dm.ultrabeamDirectionSet && status.Ultrabeam.Direction != 0 {
|
if !dm.ultrabeamDirectionSet && status.Ultrabeam.Direction != 0 {
|
||||||
directionToUse = status.Ultrabeam.Direction
|
directionToUse = status.Ultrabeam.Direction
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check cooldown to prevent rapid fire commands
|
// Check cooldown to prevent rapid fire commands
|
||||||
timeSinceLastUpdate := time.Since(dm.lastFreqUpdateTime)
|
timeSinceLastUpdate := time.Since(dm.lastFreqUpdateTime)
|
||||||
if timeSinceLastUpdate < dm.freqUpdateCooldown {
|
if timeSinceLastUpdate < dm.freqUpdateCooldown {
|
||||||
log.Printf("Auto-track: Cooldown active (%v remaining), skipping update", dm.freqUpdateCooldown-timeSinceLastUpdate)
|
log.Printf("Auto-track: Cooldown active (%v remaining), skipping update", dm.freqUpdateCooldown-timeSinceLastUpdate)
|
||||||
} else {
|
|
||||||
log.Printf("Auto-track (%s): Frequency differs by %d kHz, updating Ultrabeam to %d kHz (direction=%d)", radioSource, freqDiff, radioFreqKhz, directionToUse)
|
|
||||||
|
|
||||||
// Send to Ultrabeam with saved or current direction
|
|
||||||
if err := dm.ultrabeam.SetFrequency(radioFreqKhz, directionToUse); err != nil {
|
|
||||||
log.Printf("Auto-track: Failed to update Ultrabeam: %v (will retry)", err)
|
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Auto-track: Successfully sent frequency to Ultrabeam")
|
log.Printf("Auto-track (%s): Frequency differs by %d kHz, updating Ultrabeam to %d kHz (direction=%d)", radioSource, freqDiff, radioFreqKhz, directionToUse)
|
||||||
dm.lastFreqUpdateTime = time.Now() // Update cooldown timer
|
|
||||||
|
// Send to Ultrabeam with saved or current direction
|
||||||
|
if err := dm.ultrabeam.SetFrequency(radioFreqKhz, directionToUse); err != nil {
|
||||||
|
log.Printf("Auto-track: Failed to update Ultrabeam: %v (will retry)", err)
|
||||||
|
} else {
|
||||||
|
log.Printf("Auto-track: Successfully sent frequency to Ultrabeam")
|
||||||
|
dm.lastFreqUpdateTime = time.Now() // Update cooldown timer
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If out of range, simply skip auto-track but continue with status broadcast
|
||||||
}
|
}
|
||||||
// If out of range, simply skip auto-track but continue with status broadcast
|
|
||||||
|
// Solar Data (fetched every 15 minutes, cached)
|
||||||
|
if solarData, err := dm.solarClient.GetSolarData(); err == nil {
|
||||||
|
status.Solar = solarData
|
||||||
|
} else {
|
||||||
|
log.Printf("Solar data error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Weather Data (fetched every 10 minutes, cached)
|
||||||
|
if weatherData, err := dm.weatherClient.GetWeatherData(); err == nil {
|
||||||
|
status.Weather = weatherData
|
||||||
|
} else {
|
||||||
|
log.Printf("Weather data error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update cached status
|
||||||
|
dm.statusMu.Lock()
|
||||||
|
dm.lastStatus = status
|
||||||
|
dm.statusMu.Unlock()
|
||||||
|
|
||||||
|
// Broadcast to all connected clients
|
||||||
|
if dm.hub != nil {
|
||||||
|
dm.hub.BroadcastStatusUpdate(status)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Solar Data (fetched every 15 minutes, cached)
|
|
||||||
if solarData, err := dm.solarClient.GetSolarData(); err == nil {
|
|
||||||
status.Solar = solarData
|
|
||||||
} else {
|
|
||||||
log.Printf("Solar data error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Weather Data (fetched every 10 minutes, cached)
|
|
||||||
if weatherData, err := dm.weatherClient.GetWeatherData(); err == nil {
|
|
||||||
status.Weather = weatherData
|
|
||||||
} else {
|
|
||||||
log.Printf("Weather data error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update cached status
|
|
||||||
dm.statusMu.Lock()
|
|
||||||
dm.lastStatus = status
|
|
||||||
dm.statusMu.Unlock()
|
|
||||||
|
|
||||||
// Broadcast to all connected clients
|
|
||||||
if dm.hub != nil {
|
|
||||||
dm.hub.BroadcastStatusUpdate(status)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dm *DeviceManager) GetStatus() *SystemStatus {
|
func (dm *DeviceManager) GetStatus() *SystemStatus {
|
||||||
|
|||||||
@@ -20,10 +20,6 @@ type Client struct {
|
|||||||
connMu sync.Mutex // For connection management
|
connMu sync.Mutex // For connection management
|
||||||
writeMu sync.Mutex // For writing to connection (separate from reads)
|
writeMu sync.Mutex // For writing to connection (separate from reads)
|
||||||
|
|
||||||
interlockID string
|
|
||||||
interlockName string
|
|
||||||
interlockMu sync.RWMutex
|
|
||||||
|
|
||||||
lastStatus *Status
|
lastStatus *Status
|
||||||
statusMu sync.RWMutex
|
statusMu sync.RWMutex
|
||||||
|
|
||||||
@@ -34,27 +30,20 @@ type Client struct {
|
|||||||
stopChan chan struct{}
|
stopChan chan struct{}
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
checkTransmitAllowed func() bool
|
onFrequencyChange func(freqMHz float64)
|
||||||
onFrequencyChange func(freqMHz float64)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(host string, port int, interlockName string) *Client {
|
func New(host string, port int, interlockName string) *Client {
|
||||||
return &Client{
|
return &Client{
|
||||||
host: host,
|
host: host,
|
||||||
port: port,
|
port: port,
|
||||||
interlockName: interlockName,
|
stopChan: make(chan struct{}),
|
||||||
stopChan: make(chan struct{}),
|
|
||||||
lastStatus: &Status{
|
lastStatus: &Status{
|
||||||
Connected: false,
|
Connected: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
// SetFrequencyChangeCallback sets the callback function called when frequency changes
|
||||||
func (c *Client) SetFrequencyChangeCallback(callback func(freqMHz float64)) {
|
func (c *Client) SetFrequencyChangeCallback(callback func(freqMHz float64)) {
|
||||||
c.onFrequencyChange = callback
|
c.onFrequencyChange = callback
|
||||||
@@ -104,10 +93,11 @@ func (c *Client) Start() error {
|
|||||||
// Start message listener
|
// Start message listener
|
||||||
go c.messageLoop()
|
go c.messageLoop()
|
||||||
|
|
||||||
// Create interlock (no sleep needed, connection is synchronous)
|
// Subscribe to slice updates for frequency tracking
|
||||||
if err := c.createInterlock(); err != nil {
|
log.Println("FlexRadio: Subscribing to slice updates...")
|
||||||
log.Printf("FlexRadio: Failed to create interlock: %v", err)
|
_, err := c.sendCommand("sub slice all")
|
||||||
return err
|
if err != nil {
|
||||||
|
log.Printf("FlexRadio: Warning - failed to subscribe to slices: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -223,7 +213,6 @@ func (c *Client) messageLoop() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("FlexRadio RX: %s", line)
|
|
||||||
c.handleMessage(line)
|
c.handleMessage(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +222,6 @@ func (c *Client) messageLoop() {
|
|||||||
func (c *Client) handleMessage(msg string) {
|
func (c *Client) handleMessage(msg string) {
|
||||||
// Response format: R<seq>|<status>|<data>
|
// Response format: R<seq>|<status>|<data>
|
||||||
if strings.HasPrefix(msg, "R") {
|
if strings.HasPrefix(msg, "R") {
|
||||||
c.handleResponse(msg)
|
|
||||||
return
|
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) {
|
func (c *Client) handleStatus(msg string) {
|
||||||
// Format: S<handle>|<key>=<value> ...
|
// Format: S<handle>|<key>=<value> ...
|
||||||
parts := strings.SplitN(msg, "|", 2)
|
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)
|
// Check for slice updates (frequency changes)
|
||||||
if strings.Contains(msg, "slice") {
|
if strings.Contains(msg, "slice") {
|
||||||
if rfFreq, ok := statusMap["RF_frequency"]; ok {
|
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) {
|
func (c *Client) GetStatus() (*Status, error) {
|
||||||
c.statusMu.RLock()
|
c.statusMu.RLock()
|
||||||
defer c.statusMu.RUnlock()
|
defer c.statusMu.RUnlock()
|
||||||
@@ -419,32 +304,3 @@ func (c *Client) GetStatus() (*Status, error) {
|
|||||||
|
|
||||||
return &status, nil
|
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 newHeading = status.heading;
|
||||||
const oldHeading = heading;
|
const oldHeading = heading;
|
||||||
|
|
||||||
console.log(`RotatorGenius heading update: ${oldHeading} -> ${newHeading}`);
|
|
||||||
|
|
||||||
if (heading === null) {
|
if (heading === null) {
|
||||||
// First time: accept any value
|
// First time: accept any value
|
||||||
heading = newHeading;
|
heading = newHeading;
|
||||||
@@ -56,7 +54,7 @@
|
|||||||
try {
|
try {
|
||||||
hasTarget = true; // Mark that we have a target
|
hasTarget = true; // Mark that we have a target
|
||||||
// Subtract 10 degrees to compensate for rotator momentum
|
// Subtract 10 degrees to compensate for rotator momentum
|
||||||
const adjustedHeading = (targetHeading - 10 + 360) % 360;
|
const adjustedHeading = (targetHeading + 360) % 360;
|
||||||
await api.rotator.setHeading(adjustedHeading);
|
await api.rotator.setHeading(adjustedHeading);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to set heading:', err);
|
console.error('Failed to set heading:', err);
|
||||||
|
|||||||
@@ -191,7 +191,7 @@
|
|||||||
<div class="auto-track-controls">
|
<div class="auto-track-controls">
|
||||||
<label class="toggle-label">
|
<label class="toggle-label">
|
||||||
<input type="checkbox" bind:checked={autoTrackEnabled} on:change={updateAutoTrack} />
|
<input type="checkbox" bind:checked={autoTrackEnabled} on:change={updateAutoTrack} />
|
||||||
<span>Enable Auto-Track from Tuner</span>
|
<span>Enable Auto-Track from Radio</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="threshold-group">
|
<div class="threshold-group">
|
||||||
|
|||||||
Reference in New Issue
Block a user