corrected all bugs

This commit is contained in:
2026-01-11 15:33:44 +01:00
parent 46ee44c6c9
commit 9837657dd9
10 changed files with 992 additions and 497 deletions

View File

@@ -7,6 +7,7 @@ import (
"git.rouggy.com/rouggy/ShackMaster/internal/config"
"git.rouggy.com/rouggy/ShackMaster/internal/devices/antennagenius"
"git.rouggy.com/rouggy/ShackMaster/internal/devices/flexradio"
"git.rouggy.com/rouggy/ShackMaster/internal/devices/powergenius"
"git.rouggy.com/rouggy/ShackMaster/internal/devices/rotatorgenius"
"git.rouggy.com/rouggy/ShackMaster/internal/devices/tunergenius"
@@ -25,6 +26,7 @@ type DeviceManager struct {
antennaGenius *antennagenius.Client
rotatorGenius *rotatorgenius.Client
ultrabeam *ultrabeam.Client
flexRadio *flexradio.Client
solarClient *solar.Client
weatherClient *weather.Client
@@ -42,6 +44,10 @@ type DeviceManager struct {
ultrabeamDirectionSet bool // True if user has explicitly set a direction
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 {
@@ -51,6 +57,7 @@ type SystemStatus struct {
AntennaGenius *antennagenius.Status `json:"antenna_genius"`
RotatorGenius *rotatorgenius.Status `json:"rotator_genius"`
Ultrabeam *ultrabeam.Status `json:"ultrabeam"`
FlexRadio *flexradio.Status `json:"flexradio,omitempty"`
Solar *solar.SolarData `json:"solar"`
Weather *weather.WeatherData `json:"weather"`
Timestamp time.Time `json:"timestamp"`
@@ -60,12 +67,12 @@ 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
ultrabeamDirection: 0, // Normal direction by default
freqUpdateCooldown: 2 * time.Second, // Wait 2 seconds between updates
freqThreshold: 25000, // 25 kHz default
autoTrackEnabled: true, // Enabled by default
ultrabeamDirection: 0, // Normal direction by default
freqUpdateCooldown: 500 * time.Millisecond, // 500ms cooldown (was 2sec)
}
}
@@ -107,6 +114,31 @@ func (dm *DeviceManager) Initialize() error {
dm.config.Devices.Ultrabeam.Port,
)
// Initialize FlexRadio if enabled
if dm.config.Devices.FlexRadio.Enabled {
log.Printf("Initializing FlexRadio: host=%s port=%d", dm.config.Devices.FlexRadio.Host, dm.config.Devices.FlexRadio.Port)
dm.flexRadio = flexradio.New(
dm.config.Devices.FlexRadio.Host,
dm.config.Devices.FlexRadio.Port,
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
return motorsMoving == 0
})
// Set callback for immediate frequency changes (no waiting for update cycle)
dm.flexRadio.SetFrequencyChangeCallback(func(freqMHz float64) {
dm.handleFrequencyChange(freqMHz)
})
}
// Initialize Solar data client
dm.solarClient = solar.New()
@@ -154,6 +186,17 @@ func (dm *DeviceManager) Initialize() error {
}()
log.Println("Ultrabeam goroutine launched")
// Start FlexRadio if enabled
if dm.flexRadio != nil {
log.Println("Starting FlexRadio connection...")
go func() {
if err := dm.flexRadio.Start(); err != nil {
log.Printf("Warning: Failed to start FlexRadio: %v", err)
}
}()
log.Println("FlexRadio goroutine launched")
}
log.Println("Device manager initialized")
return nil
}
@@ -164,6 +207,69 @@ func (dm *DeviceManager) Start() error {
return nil
}
// handleFrequencyChange is called immediately when FlexRadio frequency changes
// This provides instant auto-track response instead of waiting for updateStatus cycle
func (dm *DeviceManager) handleFrequencyChange(freqMHz float64) {
// Check if ultrabeam is initialized
if dm.ultrabeam == nil {
return
}
// Check cooldown first
timeSinceLastUpdate := time.Since(dm.lastFreqUpdateTime)
if timeSinceLastUpdate < dm.freqUpdateCooldown {
return
}
// Use cached status instead of calling GetStatus (which can block)
dm.statusMu.RLock()
hasStatus := dm.lastStatus != nil
var ubStatus *ultrabeam.Status
if hasStatus {
ubStatus = dm.lastStatus.Ultrabeam
}
dm.statusMu.RUnlock()
if ubStatus == nil || !ubStatus.Connected {
return
}
// Don't update if motors are already moving
if ubStatus.MotorsMoving != 0 {
return
}
freqKhz := int(freqMHz * 1000)
ultrabeamFreqKhz := ubStatus.Frequency
// Only track if in Ultrabeam range (7-54 MHz)
if freqKhz < 7000 || freqKhz > 54000 {
return
}
freqDiff := freqKhz - ultrabeamFreqKhz
if freqDiff < 0 {
freqDiff = -freqDiff
}
freqDiffHz := freqDiff * 1000
if freqDiffHz >= dm.freqThreshold {
directionToUse := dm.ultrabeamDirection
if !dm.ultrabeamDirectionSet && ubStatus.Direction != 0 {
directionToUse = ubStatus.Direction
}
log.Printf("Auto-track (immediate): Updating to %d kHz (diff=%d kHz)", freqKhz, freqDiff)
if err := dm.ultrabeam.SetFrequency(freqKhz, directionToUse); err != nil {
log.Printf("Auto-track (immediate): Failed: %v", err)
} else {
dm.lastFreqUpdateTime = time.Now()
}
}
}
func (dm *DeviceManager) Stop() {
log.Println("Stopping device manager...")
close(dm.stopChan)
@@ -250,19 +356,57 @@ func (dm *DeviceManager) updateStatus() {
if !dm.ultrabeamDirectionSet {
dm.ultrabeamDirection = ubStatus.Direction
}
// Cache motors state for FlexRadio interlock callback
dm.ultrabeamStateMu.Lock()
previousMotors := dm.ultrabeamMotorsMoving
dm.ultrabeamMotorsMoving = ubStatus.MotorsMoving
dm.ultrabeamStateMu.Unlock()
// Log motor state changes
if previousMotors != ubStatus.MotorsMoving {
if ubStatus.MotorsMoving > 0 {
log.Printf("Ultrabeam: Motors STARTED (bitmask=%d)", ubStatus.MotorsMoving)
} else {
log.Printf("Ultrabeam: Motors STOPPED")
}
}
} else {
log.Printf("Ultrabeam error: %v", err)
}
// Auto frequency tracking: Update Ultrabeam when TunerGenius frequency differs from Ultrabeam
if dm.autoTrackEnabled && status.TunerGenius != nil && status.TunerGenius.Connected && status.Ultrabeam != nil && status.Ultrabeam.Connected {
tunerFreqKhz := int(status.TunerGenius.FreqA) // TunerGenius frequency is already in kHz
// FlexRadio (use direct cache access to avoid mutex contention)
if dm.flexRadio != nil {
// Access lastStatus directly from FlexRadio's internal cache
// The messageLoop updates this in real-time, no need to block on GetStatus
frStatus, err := dm.flexRadio.GetStatus()
if err == nil && frStatus != nil {
status.FlexRadio = frStatus
}
}
// Auto frequency tracking: Update Ultrabeam when radio frequency differs from Ultrabeam
// Priority: FlexRadio (fast) > TunerGenius (slow backup)
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 {
// Fallback to TunerGenius frequency (already in kHz)
radioFreqKhz = int(status.TunerGenius.FreqA)
radioSource = "TunerGenius"
}
if radioFreqKhz > 0 && status.Ultrabeam != nil && status.Ultrabeam.Connected {
ultrabeamFreqKhz := status.Ultrabeam.Frequency // Ultrabeam frequency in 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
if tunerFreqKhz >= 7000 && tunerFreqKhz <= 54000 {
freqDiff := tunerFreqKhz - ultrabeamFreqKhz
if radioFreqKhz >= 7000 && radioFreqKhz <= 54000 {
freqDiff := radioFreqKhz - ultrabeamFreqKhz
if freqDiff < 0 {
freqDiff = -freqDiff
}
@@ -284,10 +428,10 @@ func (dm *DeviceManager) updateStatus() {
if timeSinceLastUpdate < dm.freqUpdateCooldown {
log.Printf("Auto-track: Cooldown active (%v remaining), skipping update", dm.freqUpdateCooldown-timeSinceLastUpdate)
} else {
log.Printf("Auto-track: Frequency differs by %d kHz, updating Ultrabeam to %d kHz (direction=%d)", freqDiff, tunerFreqKhz, directionToUse)
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(tunerFreqKhz, directionToUse); err != nil {
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")