correct bugs AG

This commit is contained in:
2026-01-10 23:33:47 +01:00
parent bcf58b208b
commit 46ee44c6c9
11 changed files with 197 additions and 246 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -7,8 +7,8 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
<script type="module" crossorigin src="/assets/index-DIrlWzGj.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DvnnYzjx.css">
<script type="module" crossorigin src="/assets/index-DHBARw4b.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-dhCTx3KU.css">
</head>
<body>
<div id="app"></div>

View File

@@ -249,7 +249,6 @@ func (dm *DeviceManager) updateStatus() {
// This prevents auto-track from using wrong direction before user changes it
if !dm.ultrabeamDirectionSet {
dm.ultrabeamDirection = ubStatus.Direction
log.Printf("Auto-track: Initialized direction from Ultrabeam: %d", dm.ultrabeamDirection)
}
} else {
log.Printf("Ultrabeam error: %v", err)
@@ -260,50 +259,45 @@ func (dm *DeviceManager) updateStatus() {
tunerFreqKhz := int(status.TunerGenius.FreqA) // TunerGenius frequency is already in kHz
ultrabeamFreqKhz := status.Ultrabeam.Frequency // Ultrabeam frequency in kHz
// Ignore invalid frequencies or out of Ultrabeam range (40M-6M)
// This prevents retraction when slice is closed (FreqA becomes 0)
// Ultrabeam VL2.3 only covers 7000-54000 kHz (40M to 6M)
if tunerFreqKhz < 7000 || tunerFreqKhz > 54000 {
return // Out of range, skip auto-track
}
freqDiff := tunerFreqKhz - ultrabeamFreqKhz
if freqDiff < 0 {
freqDiff = -freqDiff
}
// Convert diff to Hz for comparison with threshold (which is in Hz)
freqDiffHz := freqDiff * 1000
// Don't send command if motors are already moving
if status.Ultrabeam.MotorsMoving != 0 {
// Motors moving - wait for them to finish
return
}
if freqDiffHz >= dm.freqThreshold {
// Use user's explicitly set direction, or fallback to current Ultrabeam direction
directionToUse := dm.ultrabeamDirection
if !dm.ultrabeamDirectionSet && status.Ultrabeam.Direction != 0 {
directionToUse = status.Ultrabeam.Direction
// 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 freqDiff < 0 {
freqDiff = -freqDiff
}
// Check cooldown to prevent rapid fire commands
timeSinceLastUpdate := time.Since(dm.lastFreqUpdateTime)
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)
// Convert diff to Hz for comparison with threshold (which is in Hz)
freqDiffHz := freqDiff * 1000
// Send to Ultrabeam with saved or current direction
if err := dm.ultrabeam.SetFrequency(tunerFreqKhz, 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
// Don't send command if motors are already moving
if status.Ultrabeam.MotorsMoving == 0 {
if freqDiffHz >= dm.freqThreshold {
// Use user's explicitly set direction, or fallback to current Ultrabeam direction
directionToUse := dm.ultrabeamDirection
if !dm.ultrabeamDirectionSet && status.Ultrabeam.Direction != 0 {
directionToUse = status.Ultrabeam.Direction
}
// Check cooldown to prevent rapid fire commands
timeSinceLastUpdate := time.Since(dm.lastFreqUpdateTime)
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)
// Send to Ultrabeam with saved or current direction
if err := dm.ultrabeam.SetFrequency(tunerFreqKhz, 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
}
// Solar Data (fetched every 15 minutes, cached)

View File

@@ -58,6 +58,7 @@ func (s *Server) SetupRoutes() *http.ServeMux {
mux.HandleFunc("/api/ultrabeam/frequency", s.handleUltrabeamFrequency)
mux.HandleFunc("/api/ultrabeam/retract", s.handleUltrabeamRetract)
mux.HandleFunc("/api/ultrabeam/autotrack", s.handleUltrabeamAutoTrack)
mux.HandleFunc("/api/ultrabeam/direction", s.handleUltrabeamDirection)
// Tuner endpoints
mux.HandleFunc("/api/tuner/operate", s.handleTunerOperate)
@@ -66,6 +67,7 @@ func (s *Server) SetupRoutes() *http.ServeMux {
// Antenna Genius endpoints
mux.HandleFunc("/api/antenna/select", s.handleAntennaSelect)
mux.HandleFunc("/api/antenna/deselect", s.handleAntennaDeselect)
mux.HandleFunc("/api/antenna/reboot", s.handleAntennaReboot)
// Power Genius endpoints
@@ -336,6 +338,33 @@ func (s *Server) handleAntennaSelect(w http.ResponseWriter, r *http.Request) {
s.sendJSON(w, map[string]string{"status": "ok"})
}
func (s *Server) handleAntennaDeselect(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var req struct {
Port int `json:"port"`
Antenna int `json:"antenna"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
log.Printf("Deselecting antenna %d from port %d", req.Antenna, req.Port)
if err := s.deviceManager.AntennaGenius().DeselectAntenna(req.Port, req.Antenna); err != nil {
log.Printf("Failed to deselect antenna: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Printf("Successfully deselected antenna %d from port %d", req.Antenna, req.Port)
s.sendJSON(w, map[string]string{"status": "ok"})
}
func (s *Server) handleAntennaReboot(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
@@ -414,6 +443,9 @@ func (s *Server) handleUltrabeamFrequency(w http.ResponseWriter, r *http.Request
return
}
// Save direction for auto-track to use
s.deviceManager.SetUltrabeamDirection(req.Direction)
if err := s.deviceManager.Ultrabeam().SetFrequency(req.Frequency, req.Direction); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -457,6 +489,27 @@ func (s *Server) handleUltrabeamAutoTrack(w http.ResponseWriter, r *http.Request
s.sendJSON(w, map[string]string{"status": "ok"})
}
func (s *Server) handleUltrabeamDirection(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var req struct {
Direction int `json:"direction"` // 0=normal, 1=180°, 2=bi-dir
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
// Just save the direction preference for auto-track to use
s.deviceManager.SetUltrabeamDirection(req.Direction)
s.sendJSON(w, map[string]string{"status": "ok"})
}
func (s *Server) sendJSON(w http.ResponseWriter, data interface{}) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(data)

View File

@@ -178,21 +178,22 @@ func (c *Client) pollLoop() {
func (c *Client) initialize() error {
// Get antenna list
log.Println("AntennaGenius: Getting antenna list...")
antennas, err := c.getAntennaList()
if err != nil {
return fmt.Errorf("failed to get antenna list: %w", err)
}
log.Printf("AntennaGenius: Found %d antennas", len(antennas))
for i, ant := range antennas {
log.Printf("AntennaGenius: Antenna %d: number=%d, name=%s", i, ant.Number, ant.Name)
}
c.antennasMu.Lock()
c.antennas = antennas
c.antennasMu.Unlock()
// Subscribe to port updates
if err := c.subscribeToPortUpdates(); err != nil {
return fmt.Errorf("failed to subscribe: %w", err)
}
// Initialize status
// Initialize status BEFORE subscribing so parsePortStatus can update it
c.statusMu.Lock()
c.lastStatus = &Status{
PortA: &PortStatus{},
@@ -202,6 +203,23 @@ func (c *Client) initialize() error {
}
c.statusMu.Unlock()
log.Println("AntennaGenius: Status initialized, now subscribing to port updates...")
// Subscribe to port updates (this will parse and update port status)
if err := c.subscribeToPortUpdates(); err != nil {
return fmt.Errorf("failed to subscribe: %w", err)
}
// Request initial status for both ports
log.Println("AntennaGenius: Requesting additional port status...")
_, _ = c.sendCommand("port get 1") // Port A
_, _ = c.sendCommand("port get 2") // Port B
c.statusMu.RLock()
log.Printf("AntennaGenius: Initialization complete - PortA.RxAnt=%d, PortB.RxAnt=%d",
c.lastStatus.PortA.RxAnt, c.lastStatus.PortB.RxAnt)
c.statusMu.RUnlock()
return nil
}
@@ -334,6 +352,7 @@ func (c *Client) parseAntennaLine(line string) Antenna {
func (c *Client) subscribeToPortUpdates() error {
resp, err := c.sendCommand("sub port all")
if err != nil {
log.Printf("AntennaGenius: Failed to subscribe: %v", err)
return err
}
@@ -347,6 +366,7 @@ func (c *Client) subscribeToPortUpdates() error {
}
}
log.Println("AntennaGenius: Subscription complete")
return nil
}
@@ -437,6 +457,20 @@ func (c *Client) SetAntenna(port, antenna int) error {
return err
}
// DeselectAntenna deselects an antenna from a port (sets rxant=00)
// Command format: "C1|port set <port> rxant=00"
func (c *Client) DeselectAntenna(port, antenna int) error {
cmd := fmt.Sprintf("port set %d rxant=00", port)
log.Printf("AntennaGenius: Sending deselect command: %s", cmd)
resp, err := c.sendCommand(cmd)
if err != nil {
log.Printf("AntennaGenius: Deselect failed: %v", err)
return err
}
log.Printf("AntennaGenius: Deselect response: %s", resp)
return nil
}
// Reboot reboots the device
func (c *Client) Reboot() error {
_, err := c.sendCommand("reboot")

View File

@@ -20,22 +20,49 @@
async function selectAntenna(port, antennaNum) {
try {
await api.antenna.selectAntenna(port, antennaNum);
// Check if antenna is already selected on this port
const isAlreadySelected = (port === 1 && portA.rx_ant === antennaNum) ||
(port === 2 && portB.rx_ant === antennaNum);
if (isAlreadySelected) {
// Deselect: set rxant to 00
console.log(`Deselecting antenna ${antennaNum} from port ${port}`);
await api.antenna.deselectAntenna(port, antennaNum);
} else {
// Select normally
console.log(`Selecting antenna ${antennaNum} on port ${port}`);
await api.antenna.selectAntenna(port, antennaNum);
}
} catch (err) {
console.error('Failed to select antenna:', err);
alert('Failed to select antenna');
console.error('Failed to select/deselect antenna:', err);
// No popup, just log the error
}
}
// Debug TX state - only log when tx state changes, not on every update
let lastTxStateA = false;
let lastTxStateB = false;
$: if (status && (portA.tx !== lastTxStateA || portB.tx !== lastTxStateB)) {
console.log('AntennaGenius TX state changed:', {
portA_tx: portA.tx,
portB_tx: portB.tx,
portA_tx_ant: portA.tx_ant,
portB_tx_ant: portB.tx_ant
});
lastTxStateA = portA.tx;
lastTxStateB = portB.tx;
}
async function reboot() {
if (!confirm('Are you sure you want to reboot the Antenna Genius?')) {
return;
}
try {
await api.antenna.reboot();
console.log('Antenna Genius reboot command sent');
} catch (err) {
console.error('Failed to reboot:', err);
alert('Failed to reboot');
// No popup, just log
}
}
</script>
@@ -220,12 +247,6 @@
transition: all 0.3s;
}
.antenna-card.tx {
background: rgba(244, 67, 54, 0.2);
border-color: #f44336;
box-shadow: 0 0 20px rgba(244, 67, 54, 0.4);
}
.antenna-card.active-a {
background: rgba(76, 175, 80, 0.2);
border-color: #4caf50;
@@ -238,6 +259,13 @@
box-shadow: 0 0 20px rgba(33, 150, 243, 0.3);
}
/* TX must come AFTER active-a/active-b to override */
.antenna-card.tx {
background: rgba(244, 67, 54, 0.2) !important;
border-color: #f44336 !important;
box-shadow: 0 0 20px rgba(244, 67, 54, 0.4) !important;
}
.antenna-name {
font-size: 14px;
font-weight: 500;

View File

@@ -6,6 +6,16 @@
$: powerForward = status?.power_forward || 0;
$: powerReflected = status?.power_reflected || 0;
$: swr = status?.swr || 1.0;
// Debug logging
$: if (status) {
console.log('PowerGenius status update:', {
powerForward: status.power_forward,
swr: status.swr,
state: status.state,
connected: status.connected
});
}
$: voltage = status?.voltage || 0;
$: vdd = status?.vdd || 0;
$: current = status?.current || 0;
@@ -30,7 +40,7 @@
await api.power.setFanMode(mode);
} catch (err) {
console.error('Failed to set fan mode:', err);
alert('Failed to set fan mode');
// Removed alert popup - check console for errors
}
}
@@ -40,7 +50,7 @@
await api.power.setOperate(operateValue);
} catch (err) {
console.error('Failed to toggle operate:', err);
alert('Failed to toggle operate mode');
// Removed alert popup - check console for errors
}
}
</script>
@@ -323,125 +333,6 @@
margin-top: 2px;
}
.power-display {
display: flex;
flex-direction: column;
gap: 8px;
}
.power-main {
text-align: center;
}
.power-value {
font-size: 48px;
font-weight: 200;
color: var(--accent-cyan);
line-height: 1;
text-shadow: 0 0 20px rgba(79, 195, 247, 0.5);
}
.power-value .unit {
font-size: 20px;
color: var(--text-secondary);
margin-left: 4px;
}
.power-label {
font-size: 11px;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 1px;
margin-top: 4px;
}
.power-bar {
position: relative;
height: 8px;
background: var(--bg-tertiary);
border-radius: 4px;
overflow: hidden;
}
.power-bar-fill {
position: relative;
height: 100%;
background: linear-gradient(90deg, #4caf50, #ffc107, #ff9800, #f44336);
border-radius: 4px;
transition: width 0.3s ease;
}
.power-bar-glow {
position: absolute;
top: 0;
right: 0;
width: 20px;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.5));
animation: shimmer 2s infinite;
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
.power-scale {
display: flex;
justify-content: space-between;
font-size: 9px;
color: var(--text-muted);
margin-top: 4px;
}
/* SWR Circle */
.swr-container {
display: flex;
align-items: center;
gap: 10px;
}
.swr-circle {
width: 80px;
height: 80px;
border-radius: 50%;
background: radial-gradient(circle, rgba(79, 195, 247, 0.1), transparent);
border: 3px solid var(--swr-color);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-shadow: 0 0 20px var(--swr-color);
}
.swr-value {
font-size: 20px;
font-weight: 300;
color: var(--swr-color);
}
.swr-label {
font-size: 10px;
color: var(--text-muted);
text-transform: uppercase;
}
.swr-status {
flex: 1;
}
.status-text {
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.status-text.good { color: #4caf50; }
.status-text.ok { color: #ffc107; }
.status-text.warning { color: #ff9800; }
.status-text.danger { color: #f44336; }
/* Temperature */
.temp-group {
display: grid;

View File

@@ -359,22 +359,6 @@
gap: 8px;
}
.heading-input {
background: var(--bg-tertiary);
color: var(--text-primary);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 10px 12px;
font-size: 14px;
outline: none;
transition: all 0.2s;
}
.heading-input:focus {
border-color: var(--accent-cyan);
box-shadow: 0 0 0 2px rgba(79, 195, 247, 0.2);
}
.go-btn {
padding: 10px 24px;
border-radius: 4px;
@@ -434,8 +418,4 @@
box-shadow: 0 6px 16px rgba(244, 67, 54, 0.5);
}
.arrow {
font-size: 20px;
line-height: 1;
}
</style>

View File

@@ -24,7 +24,7 @@
await api.tuner.autoTune();
} catch (err) {
console.error('Failed to tune:', err);
alert('Failed to start tuning');
// Removed alert popup - check console for errors
}
}
@@ -33,7 +33,7 @@
await api.tuner.setBypass(value);
} catch (err) {
console.error('Failed to set bypass:', err);
alert('Failed to set bypass');
// Removed alert popup - check console for errors
}
}
@@ -42,7 +42,7 @@
await api.tuner.setOperate(value);
} catch (err) {
console.error('Failed to set operate:', err);
alert('Failed to set operate');
// Removed alert popup - check console for errors
}
}
</script>
@@ -307,59 +307,14 @@
margin-top: 2px;
}
.power-display {
display: flex;
flex-direction: column;
gap: 8px;
}
/* SWR Circle */
.swr-container {
display: flex;
align-items: center;
gap: 10px;
}
.swr-circle {
width: 80px;
height: 80px;
border-radius: 50%;
background: radial-gradient(circle, rgba(79, 195, 247, 0.1), transparent);
border: 3px solid var(--swr-color);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-shadow: 0 0 20px var(--swr-color);
}
.swr-value {
font-size: 20px;
font-weight: 300;
color: var(--swr-color);
}
.swr-label {
font-size: 10px;
color: var(--text-muted);
text-transform: uppercase;
}
.swr-status {
flex: 1;
}
.status-text {
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.status-text.good { color: #4caf50; }
.status-text.ok { color: #ffc107; }
.status-text.warning { color: #ff9800; }
.status-text.danger { color: #f44336; }
/* Capacitors */
.capacitors {

View File

@@ -64,6 +64,10 @@ export const api = {
method: 'POST',
body: JSON.stringify({ port, antenna }),
}),
deselectAntenna: (port, antenna) => request('/antenna/deselect', {
method: 'POST',
body: JSON.stringify({ port, antenna }),
}),
reboot: () => request('/antenna/reboot', { method: 'POST' }),
},
@@ -101,5 +105,9 @@ export const api = {
method: 'POST',
body: JSON.stringify({ enabled, threshold }),
}),
setDirection: (direction) => request('/ultrabeam/direction', {
method: 'POST',
body: JSON.stringify({ direction }),
}),
},
};