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.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <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"> <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> <script type="module" crossorigin src="/assets/index-DHBARw4b.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DvnnYzjx.css"> <link rel="stylesheet" crossorigin href="/assets/index-dhCTx3KU.css">
</head> </head>
<body> <body>
<div id="app"></div> <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 // This prevents auto-track from using wrong direction before user changes it
if !dm.ultrabeamDirectionSet { if !dm.ultrabeamDirectionSet {
dm.ultrabeamDirection = ubStatus.Direction dm.ultrabeamDirection = ubStatus.Direction
log.Printf("Auto-track: Initialized direction from Ultrabeam: %d", dm.ultrabeamDirection)
} }
} else { } else {
log.Printf("Ultrabeam error: %v", err) log.Printf("Ultrabeam error: %v", err)
@@ -260,13 +259,9 @@ func (dm *DeviceManager) updateStatus() {
tunerFreqKhz := int(status.TunerGenius.FreqA) // TunerGenius frequency is already in kHz tunerFreqKhz := int(status.TunerGenius.FreqA) // TunerGenius frequency is already in kHz
ultrabeamFreqKhz := status.Ultrabeam.Frequency // Ultrabeam frequency in kHz ultrabeamFreqKhz := status.Ultrabeam.Frequency // Ultrabeam frequency in kHz
// Ignore invalid frequencies or out of Ultrabeam range (40M-6M) // 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) // This prevents retraction when slice is closed (FreqA becomes 0) or on out-of-range bands
// Ultrabeam VL2.3 only covers 7000-54000 kHz (40M to 6M) if tunerFreqKhz >= 7000 && tunerFreqKhz <= 54000 {
if tunerFreqKhz < 7000 || tunerFreqKhz > 54000 {
return // Out of range, skip auto-track
}
freqDiff := tunerFreqKhz - ultrabeamFreqKhz freqDiff := tunerFreqKhz - ultrabeamFreqKhz
if freqDiff < 0 { if freqDiff < 0 {
freqDiff = -freqDiff freqDiff = -freqDiff
@@ -276,11 +271,7 @@ func (dm *DeviceManager) updateStatus() {
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 {
// Motors moving - wait for them to finish
return
}
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
@@ -305,6 +296,9 @@ func (dm *DeviceManager) updateStatus() {
} }
} }
} }
}
// If out of range, simply skip auto-track but continue with status broadcast
}
// Solar Data (fetched every 15 minutes, cached) // Solar Data (fetched every 15 minutes, cached)
if solarData, err := dm.solarClient.GetSolarData(); err == nil { if solarData, err := dm.solarClient.GetSolarData(); err == nil {

View File

@@ -58,6 +58,7 @@ func (s *Server) SetupRoutes() *http.ServeMux {
mux.HandleFunc("/api/ultrabeam/frequency", s.handleUltrabeamFrequency) mux.HandleFunc("/api/ultrabeam/frequency", s.handleUltrabeamFrequency)
mux.HandleFunc("/api/ultrabeam/retract", s.handleUltrabeamRetract) mux.HandleFunc("/api/ultrabeam/retract", s.handleUltrabeamRetract)
mux.HandleFunc("/api/ultrabeam/autotrack", s.handleUltrabeamAutoTrack) mux.HandleFunc("/api/ultrabeam/autotrack", s.handleUltrabeamAutoTrack)
mux.HandleFunc("/api/ultrabeam/direction", s.handleUltrabeamDirection)
// Tuner endpoints // Tuner endpoints
mux.HandleFunc("/api/tuner/operate", s.handleTunerOperate) mux.HandleFunc("/api/tuner/operate", s.handleTunerOperate)
@@ -66,6 +67,7 @@ func (s *Server) SetupRoutes() *http.ServeMux {
// Antenna Genius endpoints // Antenna Genius endpoints
mux.HandleFunc("/api/antenna/select", s.handleAntennaSelect) mux.HandleFunc("/api/antenna/select", s.handleAntennaSelect)
mux.HandleFunc("/api/antenna/deselect", s.handleAntennaDeselect)
mux.HandleFunc("/api/antenna/reboot", s.handleAntennaReboot) mux.HandleFunc("/api/antenna/reboot", s.handleAntennaReboot)
// Power Genius endpoints // 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"}) 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) { func (s *Server) handleAntennaReboot(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
@@ -414,6 +443,9 @@ func (s *Server) handleUltrabeamFrequency(w http.ResponseWriter, r *http.Request
return 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 { if err := s.deviceManager.Ultrabeam().SetFrequency(req.Frequency, req.Direction); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@@ -457,6 +489,27 @@ func (s *Server) handleUltrabeamAutoTrack(w http.ResponseWriter, r *http.Request
s.sendJSON(w, map[string]string{"status": "ok"}) 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{}) { func (s *Server) sendJSON(w http.ResponseWriter, data interface{}) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(data) json.NewEncoder(w).Encode(data)

View File

@@ -178,21 +178,22 @@ func (c *Client) pollLoop() {
func (c *Client) initialize() error { func (c *Client) initialize() error {
// Get antenna list // Get antenna list
log.Println("AntennaGenius: Getting antenna list...")
antennas, err := c.getAntennaList() antennas, err := c.getAntennaList()
if err != nil { if err != nil {
return fmt.Errorf("failed to get antenna list: %w", err) 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.antennasMu.Lock()
c.antennas = antennas c.antennas = antennas
c.antennasMu.Unlock() c.antennasMu.Unlock()
// Subscribe to port updates // Initialize status BEFORE subscribing so parsePortStatus can update it
if err := c.subscribeToPortUpdates(); err != nil {
return fmt.Errorf("failed to subscribe: %w", err)
}
// Initialize status
c.statusMu.Lock() c.statusMu.Lock()
c.lastStatus = &Status{ c.lastStatus = &Status{
PortA: &PortStatus{}, PortA: &PortStatus{},
@@ -202,6 +203,23 @@ func (c *Client) initialize() error {
} }
c.statusMu.Unlock() 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 return nil
} }
@@ -334,6 +352,7 @@ func (c *Client) parseAntennaLine(line string) Antenna {
func (c *Client) subscribeToPortUpdates() error { func (c *Client) subscribeToPortUpdates() error {
resp, err := c.sendCommand("sub port all") resp, err := c.sendCommand("sub port all")
if err != nil { if err != nil {
log.Printf("AntennaGenius: Failed to subscribe: %v", err)
return err return err
} }
@@ -347,6 +366,7 @@ func (c *Client) subscribeToPortUpdates() error {
} }
} }
log.Println("AntennaGenius: Subscription complete")
return nil return nil
} }
@@ -437,6 +457,20 @@ func (c *Client) SetAntenna(port, antenna int) error {
return err 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 // Reboot reboots the device
func (c *Client) Reboot() error { func (c *Client) Reboot() error {
_, err := c.sendCommand("reboot") _, err := c.sendCommand("reboot")

View File

@@ -20,11 +20,37 @@
async function selectAntenna(port, antennaNum) { async function selectAntenna(port, antennaNum) {
try { try {
// 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); await api.antenna.selectAntenna(port, antennaNum);
} catch (err) {
console.error('Failed to select antenna:', err);
alert('Failed to select antenna');
} }
} catch (err) {
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() { async function reboot() {
@@ -33,9 +59,10 @@
} }
try { try {
await api.antenna.reboot(); await api.antenna.reboot();
console.log('Antenna Genius reboot command sent');
} catch (err) { } catch (err) {
console.error('Failed to reboot:', err); console.error('Failed to reboot:', err);
alert('Failed to reboot'); // No popup, just log
} }
} }
</script> </script>
@@ -220,12 +247,6 @@
transition: all 0.3s; 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 { .antenna-card.active-a {
background: rgba(76, 175, 80, 0.2); background: rgba(76, 175, 80, 0.2);
border-color: #4caf50; border-color: #4caf50;
@@ -238,6 +259,13 @@
box-shadow: 0 0 20px rgba(33, 150, 243, 0.3); 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 { .antenna-name {
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;

View File

@@ -6,6 +6,16 @@
$: powerForward = status?.power_forward || 0; $: powerForward = status?.power_forward || 0;
$: powerReflected = status?.power_reflected || 0; $: powerReflected = status?.power_reflected || 0;
$: swr = status?.swr || 1.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; $: voltage = status?.voltage || 0;
$: vdd = status?.vdd || 0; $: vdd = status?.vdd || 0;
$: current = status?.current || 0; $: current = status?.current || 0;
@@ -30,7 +40,7 @@
await api.power.setFanMode(mode); await api.power.setFanMode(mode);
} catch (err) { } catch (err) {
console.error('Failed to set fan mode:', 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); await api.power.setOperate(operateValue);
} catch (err) { } catch (err) {
console.error('Failed to toggle operate:', err); console.error('Failed to toggle operate:', err);
alert('Failed to toggle operate mode'); // Removed alert popup - check console for errors
} }
} }
</script> </script>
@@ -323,125 +333,6 @@
margin-top: 2px; 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 */ /* Temperature */
.temp-group { .temp-group {
display: grid; display: grid;

View File

@@ -359,22 +359,6 @@
gap: 8px; 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 { .go-btn {
padding: 10px 24px; padding: 10px 24px;
border-radius: 4px; border-radius: 4px;
@@ -434,8 +418,4 @@
box-shadow: 0 6px 16px rgba(244, 67, 54, 0.5); box-shadow: 0 6px 16px rgba(244, 67, 54, 0.5);
} }
.arrow {
font-size: 20px;
line-height: 1;
}
</style> </style>

View File

@@ -24,7 +24,7 @@
await api.tuner.autoTune(); await api.tuner.autoTune();
} catch (err) { } catch (err) {
console.error('Failed to tune:', 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); await api.tuner.setBypass(value);
} catch (err) { } catch (err) {
console.error('Failed to set bypass:', 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); await api.tuner.setOperate(value);
} catch (err) { } catch (err) {
console.error('Failed to set operate:', err); console.error('Failed to set operate:', err);
alert('Failed to set operate'); // Removed alert popup - check console for errors
} }
} }
</script> </script>
@@ -307,59 +307,14 @@
margin-top: 2px; margin-top: 2px;
} }
.power-display {
display: flex;
flex-direction: column;
gap: 8px;
}
/* SWR Circle */ /* 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 */
.capacitors { .capacitors {

View File

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